#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/limits.h>
#include "bhndb_private.h"
#include "bhndbvar.h"
static int bhndb_dma_tag_create(device_t dev, bus_dma_tag_t parent_dmat,
const struct bhnd_dma_translation *translation,
bus_dma_tag_t *dmat);
int
bhndb_attach_bridge(device_t parent, device_t *bhndb, int unit)
{
int error;
*bhndb = device_add_child(parent, "bhndb", unit);
if (*bhndb == NULL)
return (ENXIO);
if (!(error = device_probe_and_attach(*bhndb)))
return (0);
if ((device_delete_child(parent, *bhndb)))
device_printf(parent, "failed to detach bhndb child\n");
return (error);
}
static void
bhndb_do_suspend_resources(device_t dev, struct resource_list *rl)
{
struct resource_list_entry *rle;
STAILQ_FOREACH(rle, rl, link) {
if (rle->res == NULL)
continue;
BHNDB_SUSPEND_RESOURCE(device_get_parent(dev), dev, rle->type,
rle->res);
}
}
int
bhnd_generic_br_suspend_child(device_t dev, device_t child)
{
struct resource_list *rl;
int error;
if (device_get_parent(child) != dev)
BUS_SUSPEND_CHILD(device_get_parent(dev), child);
if (device_is_suspended(child))
return (EBUSY);
if ((error = bhnd_generic_suspend_child(dev, child)))
return (error);
rl = BUS_GET_RESOURCE_LIST(device_get_parent(child), child);
if (rl == NULL)
return (0);
bhndb_do_suspend_resources(dev, rl);
return (0);
}
int
bhnd_generic_br_resume_child(device_t dev, device_t child)
{
struct resource_list *rl;
struct resource_list_entry *rle;
int error;
if (device_get_parent(child) != dev)
BUS_RESUME_CHILD(device_get_parent(dev), child);
if (!device_is_suspended(child))
return (EBUSY);
rl = BUS_GET_RESOURCE_LIST(device_get_parent(child), child);
if (rl == NULL)
return (bhnd_generic_resume_child(dev, child));
STAILQ_FOREACH(rle, rl, link) {
if (rle->res == NULL)
continue;
error = BHNDB_RESUME_RESOURCE(device_get_parent(dev), dev,
rle->type, rle->res);
if (error) {
bhndb_do_suspend_resources(dev, rl);
return (error);
}
}
if ((error = bhnd_generic_resume_child(dev, child))) {
bhndb_do_suspend_resources(dev, rl);
}
return (error);
}
struct resource *
bhndb_host_resource_for_range(struct bhndb_host_resources *hr, int type,
rman_res_t start, rman_res_t count)
{
for (u_int i = 0; hr->resource_specs[i].type != -1; i++) {
struct resource *r = hr->resources[i];
if (hr->resource_specs[i].type != type)
continue;
if (rman_get_start(r) > start)
continue;
if (rman_get_end(r) < (start + count - 1))
continue;
return (r);
}
return (NULL);
}
struct resource *
bhndb_host_resource_for_regwin(struct bhndb_host_resources *hr,
const struct bhndb_regwin *win)
{
const struct resource_spec *rspecs;
rspecs = hr->resource_specs;
for (u_int i = 0; rspecs[i].type != -1; i++) {
if (win->res.type != rspecs[i].type)
continue;
if (win->res.rid != rspecs[i].rid)
continue;
return (hr->resources[i]);
}
device_printf(hr->owner, "missing regwin resource spec "
"(type=%d, rid=%d)\n", win->res.type, win->res.rid);
return (NULL);
}
struct bhndb_resources *
bhndb_alloc_resources(device_t dev, device_t parent_dev,
const struct bhndb_hwcfg *cfg)
{
struct bhndb_resources *r;
const struct bhndb_regwin *win;
bus_size_t last_window_size;
int rnid;
int error;
bool free_ht_mem, free_br_mem, free_br_irq;
free_ht_mem = false;
free_br_mem = false;
free_br_irq = false;
r = malloc(sizeof(*r), M_BHND, M_NOWAIT|M_ZERO);
if (r == NULL)
return (NULL);
r->dev = dev;
r->cfg = cfg;
r->res = NULL;
r->min_prio = BHNDB_PRIORITY_NONE;
STAILQ_INIT(&r->bus_regions);
STAILQ_INIT(&r->bus_intrs);
mtx_init(&r->dw_steal_mtx, device_get_nameunit(dev),
"bhndb dwa_steal lock", MTX_SPIN);
r->ht_mem_rman.rm_start = 0;
r->ht_mem_rman.rm_end = ~0;
r->ht_mem_rman.rm_type = RMAN_ARRAY;
r->ht_mem_rman.rm_descr = "BHNDB host memory";
if ((error = rman_init(&r->ht_mem_rman))) {
device_printf(r->dev, "could not initialize ht_mem_rman\n");
goto failed;
}
free_ht_mem = true;
r->br_mem_rman.rm_start = 0;
r->br_mem_rman.rm_end = BUS_SPACE_MAXADDR_32BIT;
r->br_mem_rman.rm_type = RMAN_ARRAY;
r->br_mem_rman.rm_descr = "BHNDB bridged memory";
if ((error = rman_init(&r->br_mem_rman))) {
device_printf(r->dev, "could not initialize br_mem_rman\n");
goto failed;
}
free_br_mem = true;
error = rman_manage_region(&r->br_mem_rman, 0, BUS_SPACE_MAXADDR_32BIT);
if (error) {
device_printf(r->dev, "could not configure br_mem_rman\n");
goto failed;
}
r->br_irq_rman.rm_start = 0;
r->br_irq_rman.rm_end = RM_MAX_END;
r->br_irq_rman.rm_type = RMAN_ARRAY;
r->br_irq_rman.rm_descr = "BHNDB bridged interrupts";
if ((error = rman_init(&r->br_irq_rman))) {
device_printf(r->dev, "could not initialize br_irq_rman\n");
goto failed;
}
free_br_irq = true;
error = rman_manage_region(&r->br_irq_rman, 0, RM_MAX_END);
if (error) {
device_printf(r->dev, "could not configure br_irq_rman\n");
goto failed;
}
r->dwa_count = bhndb_regwin_count(cfg->register_windows,
BHNDB_REGWIN_T_DYN);
if (r->dwa_count >= INT_MAX) {
device_printf(r->dev, "max dynamic regwin count exceeded\n");
goto failed;
}
r->dw_alloc = malloc(sizeof(r->dw_alloc[0]) * r->dwa_count, M_BHND,
M_NOWAIT);
if (r->dw_alloc == NULL)
goto failed;
r->dwa_freelist = bit_alloc(r->dwa_count, M_BHND, M_NOWAIT);
if (r->dwa_freelist == NULL)
goto failed;
rnid = 0;
last_window_size = 0;
for (win = cfg->register_windows;
win->win_type != BHNDB_REGWIN_T_INVALID; win++)
{
struct bhndb_dw_alloc *dwa;
if (win->win_type != BHNDB_REGWIN_T_DYN)
continue;
if (win->win_size == 0) {
device_printf(r->dev, "ignoring zero-length dynamic "
"register window\n");
continue;
} else if (last_window_size == 0) {
last_window_size = win->win_size;
} else if (last_window_size != win->win_size) {
device_printf(r->dev, "devices that vend multiple "
"dynamic register window sizes are not currently "
"supported\n");
goto failed;
}
dwa = &r->dw_alloc[rnid];
dwa->win = win;
dwa->parent_res = NULL;
dwa->rnid = rnid;
dwa->target = 0x0;
LIST_INIT(&dwa->refs);
rnid++;
}
error = bhndb_alloc_host_resources(&r->res, dev, parent_dev, r->cfg);
if (error) {
device_printf(r->dev,
"could not allocate host resources on %s: %d\n",
device_get_nameunit(parent_dev), error);
goto failed;
}
for (size_t i = 0; i < r->dwa_count; i++) {
struct bhndb_dw_alloc *dwa;
const struct bhndb_regwin *win;
dwa = &r->dw_alloc[i];
win = dwa->win;
dwa->parent_res = bhndb_host_resource_for_regwin(r->res, win);
if (dwa->parent_res == NULL) {
device_printf(r->dev, "no host resource found for %u "
"register window with offset %#jx and "
"size %#jx\n",
win->win_type,
(uintmax_t)win->win_offset,
(uintmax_t)win->win_size);
error = ENXIO;
goto failed;
}
if (rman_get_size(dwa->parent_res) < win->win_offset +
win->win_size)
{
device_printf(r->dev, "resource %d too small for "
"register window with offset %llx and size %llx\n",
rman_get_rid(dwa->parent_res),
(unsigned long long) win->win_offset,
(unsigned long long) win->win_size);
error = EINVAL;
goto failed;
}
}
for (u_int i = 0; r->res->resource_specs[i].type != -1; i++) {
struct resource *res;
if (r->res->resource_specs[i].type != SYS_RES_MEMORY)
continue;
res = r->res->resources[i];
error = rman_manage_region(&r->ht_mem_rman,
rman_get_start(res), rman_get_end(res));
if (error) {
device_printf(r->dev,
"could not register host memory region with "
"ht_mem_rman: %d\n", error);
goto failed;
}
}
return (r);
failed:
if (free_ht_mem)
rman_fini(&r->ht_mem_rman);
if (free_br_mem)
rman_fini(&r->br_mem_rman);
if (free_br_irq)
rman_fini(&r->br_irq_rman);
if (r->dw_alloc != NULL)
free(r->dw_alloc, M_BHND);
if (r->dwa_freelist != NULL)
free(r->dwa_freelist, M_BHND);
if (r->res != NULL)
bhndb_release_host_resources(r->res);
mtx_destroy(&r->dw_steal_mtx);
free(r, M_BHND);
return (NULL);
}
static int
bhndb_dma_tag_create(device_t dev, bus_dma_tag_t parent_dmat,
const struct bhnd_dma_translation *translation, bus_dma_tag_t *dmat)
{
bus_dma_tag_t translation_tag;
bhnd_addr_t dt_mask;
bus_addr_t lowaddr, highaddr;
bus_size_t maxsegsz;
int error;
highaddr = BUS_SPACE_MAXADDR;
maxsegsz = BUS_SPACE_MAXSIZE;
dt_mask = (translation->addr_mask | translation->addrext_mask);
KASSERT(dt_mask != 0, ("DMA addr_mask invalid: %#jx",
(uintmax_t)dt_mask));
lowaddr = MIN(dt_mask, BUS_SPACE_MAXADDR);
if (translation->addr_mask < maxsegsz)
maxsegsz = translation->addr_mask;
error = bus_dma_tag_create(parent_dmat,
1, 0,
lowaddr, highaddr,
NULL, NULL,
BUS_SPACE_MAXSIZE, 0,
maxsegsz, 0,
NULL, NULL,
&translation_tag);
if (error) {
device_printf(dev, "failed to create bridge DMA tag: %d\n",
error);
return (error);
}
*dmat = translation_tag;
return (0);
}
void
bhndb_free_resources(struct bhndb_resources *br)
{
struct bhndb_region *region, *r_next;
struct bhndb_dw_alloc *dwa;
struct bhndb_dw_rentry *dwr, *dwr_next;
struct bhndb_intr_handler *ih;
bool leaked_regions, leaked_intrs;
leaked_regions = false;
leaked_intrs = false;
if (!bhndb_dw_all_free(br)) {
for (int i = 0; i < br->dwa_count; i++) {
dwa = &br->dw_alloc[i];
if (bhndb_dw_is_free(br, dwa))
continue;
device_printf(br->dev,
"leaked dynamic register window %d\n", dwa->rnid);
leaked_regions = true;
}
}
STAILQ_FOREACH(ih, &br->bus_intrs, ih_link) {
device_printf(br->dev, "interrupt handler leaked %p\n",
ih->ih_cookiep);
}
if (leaked_intrs || leaked_regions) {
panic("leaked%s%s", leaked_intrs ? " active interrupts" : "",
leaked_regions ? " active register windows" : "");
}
if (br->res != NULL)
bhndb_release_host_resources(br->res);
for (size_t i = 0; i < br->dwa_count; i++) {
dwa = &br->dw_alloc[i];
LIST_FOREACH_SAFE(dwr, &dwa->refs, dw_link, dwr_next) {
LIST_REMOVE(dwr, dw_link);
free(dwr, M_BHND);
}
}
STAILQ_FOREACH_SAFE(region, &br->bus_regions, link, r_next) {
STAILQ_REMOVE(&br->bus_regions, region, bhndb_region, link);
free(region, M_BHND);
}
rman_fini(&br->ht_mem_rman);
rman_fini(&br->br_mem_rman);
rman_fini(&br->br_irq_rman);
free(br->dw_alloc, M_BHND);
free(br->dwa_freelist, M_BHND);
mtx_destroy(&br->dw_steal_mtx);
free(br, M_BHND);
}
int
bhndb_alloc_host_resources(struct bhndb_host_resources **resources,
device_t dev, device_t parent_dev, const struct bhndb_hwcfg *hwcfg)
{
struct bhndb_host_resources *hr;
const struct bhnd_dma_translation *dt;
bus_dma_tag_t parent_dmat;
size_t nres, ndt;
int error;
parent_dmat = bus_get_dma_tag(parent_dev);
hr = malloc(sizeof(*hr), M_BHND, M_WAITOK);
hr->owner = parent_dev;
hr->cfg = hwcfg;
hr->resource_specs = NULL;
hr->resources = NULL;
hr->dma_tags = NULL;
hr->num_dma_tags = 0;
nres = 0;
for (size_t i = 0; hwcfg->resource_specs[i].type != -1; i++)
nres++;
ndt = 0;
for (dt = hwcfg->dma_translations; dt != NULL &&
!BHND_DMA_IS_TRANSLATION_TABLE_END(dt); dt++)
{
if ((dt->base_addr & dt->addr_mask) != 0) {
device_printf(dev, "invalid DMA translation; base "
"address %#jx overlaps address mask %#jx",
(uintmax_t)dt->base_addr, (uintmax_t)dt->addr_mask);
error = EINVAL;
goto failed;
}
if ((dt->addrext_mask & dt->addr_mask) != 0) {
device_printf(dev, "invalid DMA translation; addrext "
"mask %#jx overlaps address mask %#jx",
(uintmax_t)dt->addrext_mask,
(uintmax_t)dt->addr_mask);
error = EINVAL;
goto failed;
}
ndt++;
}
hr->dma_tags = malloc(sizeof(*hr->dma_tags) * ndt, M_BHND,
M_WAITOK|M_ZERO);
for (size_t i = 0; i < ndt; i++) {
error = bhndb_dma_tag_create(dev, parent_dmat,
&hwcfg->dma_translations[i], &hr->dma_tags[i]);
if (error)
goto failed;
hr->num_dma_tags++;
}
hr->resource_specs = malloc(sizeof(hr->resource_specs[0]) * (nres + 1),
M_BHND, M_WAITOK);
for (size_t i = 0; i < nres; i++)
hr->resource_specs[i] = hwcfg->resource_specs[i];
hr->resource_specs[nres].type = -1;
hr->resources = malloc(sizeof(hr->resources[0]) * nres, M_BHND,
M_WAITOK);
error = bus_alloc_resources(hr->owner, hr->resource_specs,
hr->resources);
if (error) {
device_printf(dev, "could not allocate bridge resources via "
"%s: %d\n", device_get_nameunit(parent_dev), error);
goto failed;
}
*resources = hr;
return (0);
failed:
if (hr->resource_specs != NULL)
free(hr->resource_specs, M_BHND);
if (hr->resources != NULL)
free(hr->resources, M_BHND);
for (size_t i = 0; i < hr->num_dma_tags; i++)
bus_dma_tag_destroy(hr->dma_tags[i]);
if (hr->dma_tags != NULL)
free(hr->dma_tags, M_BHND);
free(hr, M_BHND);
return (error);
}
void
bhndb_release_host_resources(struct bhndb_host_resources *hr)
{
bus_release_resources(hr->owner, hr->resource_specs, hr->resources);
for (size_t i = 0; i < hr->num_dma_tags; i++)
bus_dma_tag_destroy(hr->dma_tags[i]);
free(hr->resources, M_BHND);
free(hr->resource_specs, M_BHND);
free(hr->dma_tags, M_BHND);
free(hr, M_BHND);
}
int
bhndb_find_hostb_core(struct bhnd_core_info *cores, u_int ncores,
bhnd_devclass_t bridge_devclass, struct bhnd_core_info *core)
{
struct bhnd_core_match md;
struct bhnd_core_info *match;
u_int match_core_idx;
md = (struct bhnd_core_match) {
BHND_MATCH_CORE_CLASS(bridge_devclass),
BHND_MATCH_CORE_UNIT(0)
};
match = NULL;
match_core_idx = UINT_MAX;
for (u_int i = 0; i < ncores; i++) {
if (!bhnd_core_matches(&cores[i], &md))
continue;
if (match != NULL && match_core_idx < match->core_idx)
continue;
match = &cores[i];
match_core_idx = match->core_idx;
}
if (match == NULL)
return (ENOENT);
*core = *match;
return (0);
}
struct bhndb_intr_isrc *
bhndb_alloc_intr_isrc(device_t owner, int rid, rman_res_t start, rman_res_t end,
rman_res_t count, u_int flags)
{
struct bhndb_intr_isrc *isrc;
isrc = malloc(sizeof(*isrc), M_BHND, M_NOWAIT);
if (isrc == NULL)
return (NULL);
isrc->is_owner = owner;
isrc->is_rid = rid;
isrc->is_res = bus_alloc_resource(owner, SYS_RES_IRQ, &isrc->is_rid,
start, end, count, flags);
if (isrc->is_res == NULL) {
free(isrc, M_BHND);
return (NULL);
}
return (isrc);
}
void
bhndb_free_intr_isrc(struct bhndb_intr_isrc *isrc)
{
bus_release_resource(isrc->is_owner, SYS_RES_IRQ, isrc->is_rid,
isrc->is_res);
free(isrc, M_BHND);
}
struct bhndb_intr_handler *
bhndb_alloc_intr_handler(device_t owner, struct resource *r,
struct bhndb_intr_isrc *isrc)
{
struct bhndb_intr_handler *ih;
ih = malloc(sizeof(*ih), M_BHND, M_NOWAIT | M_ZERO);
ih->ih_owner = owner;
ih->ih_res = r;
ih->ih_isrc = isrc;
ih->ih_cookiep = NULL;
ih->ih_active = false;
return (ih);
}
void
bhndb_free_intr_handler(struct bhndb_intr_handler *ih)
{
KASSERT(!ih->ih_active, ("free of active interrupt handler %p",
ih->ih_cookiep));
free(ih, M_BHND);
}
void
bhndb_register_intr_handler(struct bhndb_resources *br,
struct bhndb_intr_handler *ih)
{
KASSERT(!ih->ih_active, ("duplicate registration of interrupt "
"handler %p", ih->ih_cookiep));
KASSERT(ih->ih_cookiep != NULL, ("missing cookiep"));
ih->ih_active = true;
STAILQ_INSERT_HEAD(&br->bus_intrs, ih, ih_link);
}
void
bhndb_deregister_intr_handler(struct bhndb_resources *br,
struct bhndb_intr_handler *ih)
{
KASSERT(ih->ih_active, ("duplicate deregistration of interrupt "
"handler %p", ih->ih_cookiep));
KASSERT(bhndb_find_intr_handler(br, ih) == ih,
("unknown interrupt handler %p", ih));
STAILQ_REMOVE(&br->bus_intrs, ih, bhndb_intr_handler, ih_link);
ih->ih_active = false;
}
struct bhndb_intr_handler *
bhndb_find_intr_handler(struct bhndb_resources *br, void *cookiep)
{
struct bhndb_intr_handler *ih;
STAILQ_FOREACH(ih, &br->bus_intrs, ih_link) {
if (ih == cookiep)
return (ih);
}
return (NULL);
}
int
bhndb_find_resource_limits(struct bhndb_resources *br,
struct resource *r, rman_res_t *start, rman_res_t *end)
{
struct bhndb_dw_alloc *dynamic;
struct bhndb_region *sregion;
struct bhndb_intr_handler *ih;
switch (rman_get_type(r)) {
case SYS_RES_IRQ:
STAILQ_FOREACH(ih, &br->bus_intrs, ih_link) {
if (ih->ih_res == r)
continue;
*start = rman_get_start(r);
*end = rman_get_end(r);
return (0);
}
return (ENOENT);
case SYS_RES_MEMORY: {
if ((dynamic = bhndb_dw_find_resource(br, r))) {
*start = dynamic->target;
*end = dynamic->target + dynamic->win->win_size - 1;
return (0);
}
sregion = bhndb_find_resource_region(br, rman_get_start(r),
rman_get_size(r));
if (sregion != NULL && sregion->static_regwin != NULL) {
*start = sregion->addr;
*end = sregion->addr + sregion->size - 1;
return (0);
}
return (ENOENT);
}
default:
device_printf(br->dev, "unknown resource type: %d\n",
rman_get_type(r));
return (ENOENT);
}
}
int
bhndb_add_resource_region(struct bhndb_resources *br, bhnd_addr_t addr,
bhnd_size_t size, bhndb_priority_t priority, uint32_t alloc_flags,
const struct bhndb_regwin *static_regwin)
{
struct bhndb_region *reg;
reg = malloc(sizeof(*reg), M_BHND, M_NOWAIT);
if (reg == NULL)
return (ENOMEM);
*reg = (struct bhndb_region) {
.addr = addr,
.size = size,
.priority = priority,
.alloc_flags = alloc_flags,
.static_regwin = static_regwin
};
STAILQ_INSERT_HEAD(&br->bus_regions, reg, link);
return (0);
}
bool
bhndb_has_static_region_mapping(struct bhndb_resources *br,
bhnd_addr_t addr, bhnd_size_t size)
{
struct bhndb_region *region;
bhnd_addr_t r_addr;
r_addr = addr;
while ((region = bhndb_find_resource_region(br, r_addr, 1)) != NULL) {
if (region->static_regwin == NULL)
return (false);
r_addr += region->size;
if (r_addr == addr + size)
return (true);
}
return (false);
}
struct bhndb_region *
bhndb_find_resource_region(struct bhndb_resources *br, bhnd_addr_t addr,
bhnd_size_t size)
{
struct bhndb_region *region;
STAILQ_FOREACH(region, &br->bus_regions, link) {
if (addr < region->addr)
continue;
if (addr + size > region->addr + region->size)
continue;
return (region);
}
return (NULL);
}
static struct bhndb_dw_rentry *
bhndb_dw_find_resource_entry(struct bhndb_dw_alloc *dwa, struct resource *r)
{
struct bhndb_dw_rentry *rentry;
LIST_FOREACH(rentry, &dwa->refs, dw_link) {
struct resource *dw_res = rentry->dw_res;
if (rman_get_device(dw_res) != rman_get_device(r) ||
rman_get_rid(dw_res) != rman_get_rid(r) ||
rman_get_start(dw_res) != rman_get_start(r) ||
rman_get_size(dw_res) != rman_get_size(r))
{
continue;
}
return (rentry);
}
return (NULL);
}
struct bhndb_dw_alloc *
bhndb_dw_find_resource(struct bhndb_resources *br, struct resource *r)
{
struct bhndb_dw_alloc *dwa;
for (size_t i = 0; i < br->dwa_count; i++) {
dwa = &br->dw_alloc[i];
if (bhndb_dw_is_free(br, dwa))
continue;
if (bhndb_dw_find_resource_entry(dwa, r) != NULL)
return (dwa);
}
return (NULL);
}
struct bhndb_dw_alloc *
bhndb_dw_find_mapping(struct bhndb_resources *br, bhnd_addr_t addr,
bhnd_size_t size)
{
struct bhndb_dw_alloc *dwr;
const struct bhndb_regwin *win;
for (size_t i = 0; i < br->dwa_count; i++) {
dwr = &br->dw_alloc[i];
win = dwr->win;
if (addr < dwr->target)
continue;
if (addr + size > dwr->target + win->win_size)
continue;
return (dwr);
}
return (NULL);
}
int
bhndb_dw_retain(struct bhndb_resources *br, struct bhndb_dw_alloc *dwa,
struct resource *res)
{
struct bhndb_dw_rentry *rentry;
KASSERT(bhndb_dw_find_resource_entry(dwa, res) == NULL,
("double-retain of dynamic window for same resource"));
rentry = malloc(sizeof(*rentry), M_BHND, M_NOWAIT);
if (rentry == NULL)
return (ENOMEM);
rentry->dw_res = res;
LIST_INSERT_HEAD(&dwa->refs, rentry, dw_link);
bit_set(br->dwa_freelist, dwa->rnid);
return (0);
}
void
bhndb_dw_release(struct bhndb_resources *br, struct bhndb_dw_alloc *dwa,
struct resource *r)
{
struct bhndb_dw_rentry *rentry;
rentry = bhndb_dw_find_resource_entry(dwa, r);
KASSERT(rentry != NULL, ("over release of resource entry"));
LIST_REMOVE(rentry, dw_link);
free(rentry, M_BHND);
if (LIST_EMPTY(&dwa->refs))
bit_clear(br->dwa_freelist, dwa->rnid);
}
int
bhndb_dw_set_addr(device_t dev, struct bhndb_resources *br,
struct bhndb_dw_alloc *dwa, bus_addr_t addr, bus_size_t size)
{
const struct bhndb_regwin *rw;
bus_addr_t offset;
int error;
rw = dwa->win;
KASSERT(bhndb_dw_is_free(br, dwa) || mtx_owned(&br->dw_steal_mtx),
("attempting to set the target address on an in-use window"));
offset = addr % rw->win_size;
dwa->target = addr - offset;
if (rw->win_size - offset < size)
return (ENOMEM);
error = BHNDB_SET_WINDOW_ADDR(dev, dwa->win, dwa->target);
if (error) {
dwa->target = 0x0;
return (error);
}
return (0);
}
struct bhndb_dw_alloc *
bhndb_dw_steal(struct bhndb_resources *br, bus_addr_t *saved)
{
struct bhndb_dw_alloc *dw_stolen;
KASSERT(bhndb_dw_next_free(br) == NULL,
("attempting to steal an in-use window while free windows remain"));
if (br->dwa_count == 0)
return (NULL);
mtx_lock_spin(&br->dw_steal_mtx);
dw_stolen = &br->dw_alloc[0];
*saved = dw_stolen->target;
return (dw_stolen);
}
void
bhndb_dw_return_stolen(device_t dev, struct bhndb_resources *br,
struct bhndb_dw_alloc *dwa, bus_addr_t saved)
{
int error;
mtx_assert(&br->dw_steal_mtx, MA_OWNED);
error = bhndb_dw_set_addr(dev, br, dwa, saved, 0);
if (error) {
panic("failed to restore register window target %#jx: %d\n",
(uintmax_t)saved, error);
}
mtx_unlock_spin(&br->dw_steal_mtx);
}
size_t
bhndb_regwin_count(const struct bhndb_regwin *table,
bhndb_regwin_type_t type)
{
const struct bhndb_regwin *rw;
size_t count;
count = 0;
for (rw = table; rw->win_type != BHNDB_REGWIN_T_INVALID; rw++) {
if (type == BHNDB_REGWIN_T_INVALID || rw->win_type == type)
count++;
}
return (count);
}
const struct bhndb_regwin *
bhndb_regwin_find_type(const struct bhndb_regwin *table,
bhndb_regwin_type_t type, bus_size_t min_size)
{
const struct bhndb_regwin *rw;
for (rw = table; rw->win_type != BHNDB_REGWIN_T_INVALID; rw++)
{
if (rw->win_type == type && rw->win_size >= min_size)
return (rw);
}
return (NULL);
}
const struct bhndb_regwin *
bhndb_regwin_find_core(const struct bhndb_regwin *table, bhnd_devclass_t class,
int unit, bhnd_port_type port_type, u_int port, u_int region,
bus_size_t offset, bus_size_t min_size)
{
const struct bhndb_regwin *rw;
for (rw = table; rw->win_type != BHNDB_REGWIN_T_INVALID; rw++)
{
bus_size_t rw_offset;
if (rw->win_type != BHNDB_REGWIN_T_CORE)
continue;
if (rw->d.core.class != class)
continue;
if (unit != -1 && rw->d.core.unit != unit)
continue;
if (rw->d.core.port_type != port_type)
continue;
if (rw->d.core.port != port)
continue;
if (rw->d.core.region != region)
continue;
if (rw->d.core.offset > offset)
continue;
rw_offset = offset - rw->d.core.offset;
if (rw->win_size < rw_offset)
continue;
if (rw->win_size - rw_offset < min_size)
continue;
return (rw);
}
return (NULL);
}
const struct bhndb_regwin *
bhndb_regwin_find_best(const struct bhndb_regwin *table,
bhnd_devclass_t class, int unit, bhnd_port_type port_type, u_int port,
u_int region, bus_size_t offset, bus_size_t min_size)
{
const struct bhndb_regwin *rw;
rw = bhndb_regwin_find_core(table, class, unit, port_type,
port, region, offset, min_size);
if (rw != NULL)
return (rw);
return (bhndb_regwin_find_type(table, BHNDB_REGWIN_T_DYN, min_size));
}
bool
bhndb_regwin_match_core(const struct bhndb_regwin *regw,
struct bhnd_core_info *core)
{
if (regw->win_type != BHNDB_REGWIN_T_CORE)
return (false);
if (bhnd_core_class(core) != regw->d.core.class)
return (false);
if (core->unit != regw->d.core.unit)
return (false);
return (true);
}
const struct bhndb_hw_priority *
bhndb_hw_priority_find_core(const struct bhndb_hw_priority *table,
struct bhnd_core_info *core)
{
const struct bhndb_hw_priority *hp;
for (hp = table; hp->ports != NULL; hp++) {
if (bhnd_core_matches(core, &hp->match))
return (hp);
}
return (NULL);
}
const struct bhndb_port_priority *
bhndb_hw_priorty_find_port(const struct bhndb_hw_priority *table,
struct bhnd_core_info *core, bhnd_port_type port_type, u_int port,
u_int region)
{
const struct bhndb_hw_priority *hp;
if ((hp = bhndb_hw_priority_find_core(table, core)) == NULL)
return (NULL);
for (u_int i = 0; i < hp->num_ports; i++) {
const struct bhndb_port_priority *pp = &hp->ports[i];
if (pp->type != port_type)
continue;
if (pp->port != port)
continue;
if (pp->region != region)
continue;
return (pp);
}
return (NULL);
}