bhndb(4): Implement bridge support for the BCM4312 and other PCI_V0 chipsets.

Very early (PCI_V0) Broadcom PCI Wi-Fi chipsets have a few quirks when
compared to later PCI(e) core revisions:

- The standard static BAR0 mapping of the PCI core registers is discontiguous,
  with siba's cfg0 register block mapped distinctly from the other core
  registers.
- No dedicated ChipCommon register mapping is provided; instead, the
  single configurable register window must be used to access both
  ChipCommon and D11 core registers. The D11 core's operational semantics
  guarantee the safety of -- after disabling interrupts -- borrowing
  the single dynamic register window to perform the few ChipCommon
  operations required by a driver.

To support these early PCI devices:

- Allow defining multiple discontiguous BHNDB_REGWIN_T_CORE register
  windows that map a single port/region, and producing bridged resource
  allocations backed by those discontiguous windows.
- Support stealing existing register window allocations to fulfill indirect
  bhnd(4) bus I/O requests within address ranges tagged with
  BHNDB_ALLOC_FULFILL_ON_OVERCOMMIT.
- Fix an inverted test of bhndb_is_pcie_attached() that disabled
  PCI-only clock bring-up required by these devices.

Approved by:	adrian (mentor, implicit)
Sponsored by:	The FreeBSD Foundation
This commit is contained in:
Landon J. Fuller 2017-11-28 00:12:14 +00:00
parent b8bfab43ad
commit eaa5fb4b80
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=326297
9 changed files with 385 additions and 62 deletions

View File

@ -115,8 +115,8 @@ static int bhndb_try_activate_resource(
static inline struct bhndb_dw_alloc *bhndb_io_resource(struct bhndb_softc *sc,
bus_addr_t addr, bus_size_t size,
bus_size_t *offset);
bus_size_t *offset, bool *stolen,
bus_addr_t *restore);
/**
* Default bhndb(4) implementation of DEVICE_PROBE().
@ -270,6 +270,9 @@ bhndb_init_region_cfg(struct bhndb_softc *sc, bhnd_erom_t *erom,
for (regw = br->cfg->register_windows;
regw->win_type != BHNDB_REGWIN_T_INVALID; regw++)
{
const struct bhndb_port_priority *pp;
uint32_t alloc_flags;
/* Only core windows are supported */
if (regw->win_type != BHNDB_REGWIN_T_CORE)
continue;
@ -294,6 +297,18 @@ bhndb_init_region_cfg(struct bhndb_softc *sc, bhnd_erom_t *erom,
return (error);
}
/*
* Apply the register window's region offset, if any.
*/
if (regw->d.core.offset > size) {
device_printf(sc->dev, "invalid register "
"window offset %#jx for region %#jx+%#jx\n",
regw->d.core.offset, addr, size);
return (EINVAL);
}
addr += regw->d.core.offset;
/*
* Always defer to the register window's size.
*
@ -307,14 +322,25 @@ bhndb_init_region_cfg(struct bhndb_softc *sc, bhnd_erom_t *erom,
*/
size = regw->win_size;
/* Fetch allocation flags from the corresponding port
* priority entry, if any */
pp = bhndb_hw_priorty_find_port(table, core,
regw->d.core.port_type, regw->d.core.port,
regw->d.core.region);
if (pp != NULL) {
alloc_flags = pp->alloc_flags;
} else {
alloc_flags = 0;
}
/*
* Add to the bus region list.
*
* The window priority for a statically mapped
* region is always HIGH.
* The window priority for a statically mapped region is
* always HIGH.
*/
error = bhndb_add_resource_region(br, addr, size,
BHNDB_PRIORITY_HIGH, regw);
BHNDB_PRIORITY_HIGH, 0, regw);
if (error)
return (error);
}
@ -325,7 +351,6 @@ bhndb_init_region_cfg(struct bhndb_softc *sc, bhnd_erom_t *erom,
* ports defined in the priority table
*/
for (u_int i = 0; i < ncores; i++) {
struct bhndb_region *region;
struct bhnd_core_info *core;
struct bhnd_core_match md;
@ -369,13 +394,12 @@ bhndb_init_region_cfg(struct bhndb_softc *sc, bhnd_erom_t *erom,
}
/* Skip ports with an existing static mapping */
region = bhndb_find_resource_region(br, addr, size);
if (region != NULL && region->static_regwin != NULL)
if (bhndb_has_static_region_mapping(br, addr, size))
continue;
/* Define a dynamic region for this port */
error = bhndb_add_resource_region(br, addr, size,
pp->priority, NULL);
pp->priority, pp->alloc_flags, NULL);
if (error)
return (error);
@ -416,22 +440,29 @@ bhndb_init_region_cfg(struct bhndb_softc *sc, bhnd_erom_t *erom,
struct bhndb_region *region;
const char *direct_msg, *type_msg;
bhndb_priority_t prio, prio_min;
uint32_t flags;
prio_min = br->min_prio;
device_printf(sc->dev, "min_prio: %d\n", prio_min);
STAILQ_FOREACH(region, &br->bus_regions, link) {
prio = region->priority;
flags = region->alloc_flags;
direct_msg = prio >= prio_min ? "direct" : "indirect";
type_msg = region->static_regwin ? "static" : "dynamic";
device_printf(sc->dev, "region 0x%llx+0x%llx priority "
"%u %s/%s\n",
"%u %s/%s",
(unsigned long long) region->addr,
(unsigned long long) region->size,
region->priority,
direct_msg, type_msg);
if (flags & BHNDB_ALLOC_FULFILL_ON_OVERCOMMIT)
printf(" [overcommit]\n");
else
printf("\n");
}
}
@ -1605,11 +1636,12 @@ bhndb_deactivate_bhnd_resource(device_t dev, device_t child,
* in-use region; the first matching region is returned.
*/
static struct bhndb_dw_alloc *
bhndb_io_resource_slow(struct bhndb_softc *sc, bus_addr_t addr,
bus_size_t size, bus_size_t *offset)
bhndb_io_resource_slow(struct bhndb_softc *sc, bus_addr_t addr, bus_size_t size,
bus_size_t *offset, bool *stolen, bus_addr_t *restore)
{
struct bhndb_resources *br;
struct bhndb_dw_alloc *dwa;
struct bhndb_region *region;
BHNDB_LOCK_ASSERT(sc, MA_OWNED);
@ -1638,10 +1670,25 @@ bhndb_io_resource_slow(struct bhndb_softc *sc, bus_addr_t addr,
*offset = dwa->win->win_offset;
*offset += addr - dwa->target;
*stolen = false;
return (dwa);
}
/* No existing dynamic mapping found. We'll need to check for a defined
* region to determine whether we can fulfill this request by
* stealing from an existing allocated register window */
region = bhndb_find_resource_region(br, addr, size);
if (region == NULL)
return (NULL);
if ((region->alloc_flags & BHNDB_ALLOC_FULFILL_ON_OVERCOMMIT) == 0)
return (NULL);
if ((dwa = bhndb_dw_steal(br, restore)) != NULL) {
*stolen = true;
return (dwa);
}
/* not found */
return (NULL);
}
@ -1649,9 +1696,8 @@ bhndb_io_resource_slow(struct bhndb_softc *sc, bus_addr_t addr,
* Return a borrowed reference to a bridge resource allocation record capable
* of handling bus I/O requests of @p size at @p addr.
*
* This will either return a reference to an existing allocation
* record mapping the requested space, or will configure and return a free
* allocation record.
* This will either return a reference to an existing allocation record mapping
* the requested space, or will configure and return a free allocation record.
*
* Will panic if a usable record cannot be found.
*
@ -1660,10 +1706,16 @@ bhndb_io_resource_slow(struct bhndb_softc *sc, bus_addr_t addr,
* @param size The size of the I/O operation to be performed at @p addr.
* @param[out] offset The offset within the returned resource at which
* to perform the I/O request.
* @param[out] stolen Set to true if the allocation record was stolen to fulfill
* this request. If a stolen allocation record is returned,
* bhndb_io_resource_restore() must be called upon completion of the bus I/O
* request.
* @param[out] restore If the allocation record was stolen, this will be set
* to the target that must be restored.
*/
static inline struct bhndb_dw_alloc *
bhndb_io_resource(struct bhndb_softc *sc, bus_addr_t addr, bus_size_t size,
bus_size_t *offset)
bus_size_t *offset, bool *stolen, bus_addr_t *restore)
{
struct bhndb_resources *br;
struct bhndb_dw_alloc *dwa;
@ -1691,7 +1743,8 @@ bhndb_io_resource(struct bhndb_softc *sc, bus_addr_t addr, bus_size_t size,
* current operation.
*/
if (dwa == NULL) {
dwa = bhndb_io_resource_slow(sc, addr, size, offset);
dwa = bhndb_io_resource_slow(sc, addr, size, offset, stolen,
restore);
if (dwa == NULL) {
panic("register windows exhausted attempting to map "
"0x%llx-0x%llx\n",
@ -1720,6 +1773,7 @@ bhndb_io_resource(struct bhndb_softc *sc, bus_addr_t addr, bus_size_t size,
/* Calculate the offset and return */
*offset = (addr - dwa->target) + dwa->win->win_offset;
*stolen = false;
return (dwa);
}
@ -1733,12 +1787,14 @@ bhndb_io_resource(struct bhndb_softc *sc, bus_addr_t addr, bus_size_t size,
struct bhndb_dw_alloc *dwa; \
struct resource *io_res; \
bus_size_t io_offset; \
bus_addr_t restore; \
bool stolen; \
\
sc = device_get_softc(dev); \
\
BHNDB_LOCK(sc); \
dwa = bhndb_io_resource(sc, rman_get_start(r->res) + \
offset, _io_size, &io_offset); \
offset, _io_size, &io_offset, &stolen, &restore); \
io_res = dwa->parent_res; \
\
KASSERT(!r->direct, \
@ -1748,6 +1804,10 @@ bhndb_io_resource(struct bhndb_softc *sc, bus_addr_t addr, bus_size_t size,
("i/o resource is not active"));
#define BHNDB_IO_COMMON_TEARDOWN() \
if (stolen) { \
bhndb_dw_return_stolen(sc->dev, sc->bus_res, \
dwa, restore); \
} \
BHNDB_UNLOCK(sc);
/* Defines a bhndb_bus_read_* method implementation */

View File

@ -90,6 +90,7 @@ struct bhndb_regwin {
bhnd_port_type port_type; /**< mapped port type */
u_int port; /**< mapped port number */
u_int region; /**< mapped region number */
bhnd_size_t offset; /**< mapped offset within the region */
} core;
/** SPROM register window (BHNDB_REGWIN_T_SPROM). */
@ -149,6 +150,26 @@ typedef enum {
BHNDB_PRIORITY_HIGH = 3
} bhndb_priority_t;
/**
* bhndb resource allocation flags.
*/
enum bhndb_alloc_flags {
/**
* If resource overcommit prevents fulfilling a request for this
* resource, an in-use resource should be be borrowed to fulfill the
* request.
*
* The only known use case is to support accessing the ChipCommon core
* during Wi-Fi driver operation on early PCI Wi-Fi devices
* (PCI_V0, SSB) that do not provide a dedicated ChipCommon register
* window mapping; on such devices, device and firmware semantics
* guarantee the safety of -- after disabling interrupts -- borrowing
* the single dynamic register window that's been assigned to the D11
* core to perform the few ChipCommon operations required by the driver.
*/
BHNDB_ALLOC_FULFILL_ON_OVERCOMMIT = (1<<0),
};
/**
* Port resource priority descriptor.
*/
@ -157,6 +178,7 @@ struct bhndb_port_priority {
u_int port; /**< port */
u_int region; /**< region */
bhndb_priority_t priority; /**< port priority */
uint32_t alloc_flags; /**< port allocation flags (@see bhndb_alloc_flags) */
};
/**

View File

@ -76,19 +76,24 @@ __FBSDID("$FreeBSD$");
BHNDB_PORTS(__VA_ARGS__) \
}
/* Define a port priority record for the type/port/region
* triplet. */
#define BHNDB_PORT_PRIO(_type, _port, _region, _priority) { \
/* Define a port priority record for the type/port/region triplet, optionally
* specifying port allocation flags as the final argument */
#define BHNDB_PORT_PRIO(_type, _port, _region, _priority, ...) \
_BHNDB_PORT_PRIO(_type, _port, _region, _priority, ## __VA_ARGS__, 0)
#define _BHNDB_PORT_PRIO(_type, _port, _region, _priority, _flags, ...) \
{ \
.type = (BHND_PORT_ ## _type), \
.port = _port, \
.region = _region, \
.priority = (BHNDB_PRIORITY_ ## _priority) \
.priority = (BHNDB_PRIORITY_ ## _priority), \
.alloc_flags = (_flags) \
}
/* Define a port priority record for the default (_type, 0, 0) type/port/region
* triplet. */
#define BHNDB_PORT0_PRIO(_type, _priority) \
BHNDB_PORT_PRIO(_type, 0, 0, _priority)
#define BHNDB_PORT0_PRIO(_type, _priority, ...) \
BHNDB_PORT_PRIO(_type, 0, 0, _priority, ## __VA_ARGS__, 0)
/**
* Generic resource priority configuration usable with all currently supported
@ -170,10 +175,14 @@ const struct bhndb_hw_priority bhndb_siba_priority_table[] = {
* Agent ports are marked as 'NONE' on siba(4) devices, as they
* will be fully mappable via register windows shared with the
* device0.0 port.
*
* To support early PCI_V0 devices, we enable FULFILL_ON_OVERCOMMIT for
* ChipCommon.
*/
BHNDB_CLASS_PRIO(CC, LOW,
/* Device Block */
BHNDB_PORT_PRIO(DEVICE, 0, 0, LOW)
BHNDB_PORT_PRIO(DEVICE, 0, 0, LOW,
BHNDB_ALLOC_FULFILL_ON_OVERCOMMIT)
),
BHNDB_CLASS_PRIO(PMU, LOW,
@ -193,4 +202,4 @@ const struct bhndb_hw_priority bhndb_siba_priority_table[] = {
),
BHNDB_HW_PRIORITY_TABLE_END
};
};

View File

@ -707,26 +707,28 @@ bhndb_pci_sprom_size(struct bhndb_pci_softc *sc)
* Return the host resource providing a static mapping of the PCI core's
* registers.
*
* @param sc bhndb PCI driver state.
* @param[out] res On success, the host resource containing our PCI
* core's register window.
* @param[out] offset On success, the offset of the PCI core registers within
* @p res.
* @param sc bhndb PCI driver state.
* @param offset The required readable offset within the PCI core
* register block.
* @param size The required readable size at @p offset.
* @param[out] res On success, the host resource containing our PCI
* core's register window.
* @param[out] res_offset On success, the @p offset relative to @p res.
*
* @retval 0 success
* @retval ENXIO if a valid static register window mapping the PCI core
* registers is not available.
*/
static int
bhndb_pci_get_core_regs(struct bhndb_pci_softc *sc, struct resource **res,
bus_size_t *offset)
bhndb_pci_get_core_regs(struct bhndb_pci_softc *sc, bus_size_t offset,
bus_size_t size, struct resource **res, bus_size_t *res_offset)
{
const struct bhndb_regwin *win;
struct resource *r;
/* Locate the static register window mapping the PCI core */
/* Locate the static register window mapping the requested offset */
win = bhndb_regwin_find_core(sc->bhndb.bus_res->cfg->register_windows,
sc->pci_devclass, 0, BHND_PORT_DEVICE, 0, 0);
sc->pci_devclass, 0, BHND_PORT_DEVICE, 0, 0, offset, size);
if (win == NULL) {
device_printf(sc->dev, "missing PCI core register window\n");
return (ENXIO);
@ -739,8 +741,11 @@ bhndb_pci_get_core_regs(struct bhndb_pci_softc *sc, struct resource **res,
return (ENXIO);
}
KASSERT(offset >= win->d.core.offset, ("offset %#jx outside of "
"register window", (uintmax_t)offset));
*res = r;
*offset = win->win_offset;
*res_offset = win->win_offset + (offset - win->d.core.offset);
return (0);
}
@ -761,18 +766,21 @@ bhndb_pci_write_core(struct bhndb_pci_softc *sc, bus_size_t offset,
bus_size_t r_offset;
int error;
if ((error = bhndb_pci_get_core_regs(sc, &r, &r_offset)))
panic("no PCI core registers: %d", error);
error = bhndb_pci_get_core_regs(sc, offset, width, &r, &r_offset);
if (error) {
panic("no PCI register window mapping %#jx+%#x: %d",
(uintmax_t)offset, width, error);
}
switch (width) {
case 1:
bus_write_1(r, r_offset + offset, value);
bus_write_1(r, r_offset, value);
break;
case 2:
bus_write_2(r, r_offset + offset, value);
bus_write_2(r, r_offset, value);
break;
case 4:
bus_write_4(r, r_offset + offset, value);
bus_write_4(r, r_offset, value);
break;
default:
panic("invalid width: %u", width);
@ -794,16 +802,19 @@ bhndb_pci_read_core(struct bhndb_pci_softc *sc, bus_size_t offset, u_int width)
bus_size_t r_offset;
int error;
if ((error = bhndb_pci_get_core_regs(sc, &r, &r_offset)))
panic("no PCI core registers: %d", error);
error = bhndb_pci_get_core_regs(sc, offset, width, &r, &r_offset);
if (error) {
panic("no PCI register window mapping %#jx+%#x: %d",
(uintmax_t)offset, width, error);
}
switch (width) {
case 1:
return (bus_read_1(r, r_offset + offset));
return (bus_read_1(r, r_offset));
case 2:
return (bus_read_2(r, r_offset + offset));
return (bus_read_2(r, r_offset));
case 4:
return (bus_read_4(r, r_offset + offset));
return (bus_read_4(r, r_offset));
default:
panic("invalid width: %u", width);
}
@ -1057,7 +1068,7 @@ bhndb_enable_pci_clocks(device_t dev)
pci_dev = device_get_parent(dev);
/* Only supported and required on PCI devices */
if (!bhndb_is_pcie_attached(dev))
if (bhndb_is_pcie_attached(dev))
return (0);
/* Read state of XTAL pin */

View File

@ -312,7 +312,13 @@ static const struct bhndb_hwcfg bhndb_pci_hwcfg_v0 = {
.res = { SYS_RES_MEMORY, PCIR_BAR(0) }
},
/* bar0+0x1800: pci core registers */
/*
* bar0+0x1800: pci core registers.
*
* Does not include the SSB CFG registers found at the end of
* the 4K core register block; these are mapped non-contigiously
* by the next entry.
*/
{
.win_type = BHNDB_REGWIN_T_CORE,
.win_offset = BHNDB_PCI_V0_BAR0_PCIREG_OFFSET,
@ -322,10 +328,27 @@ static const struct bhndb_hwcfg bhndb_pci_hwcfg_v0 = {
.unit = 0,
.port = 0,
.region = 0,
.port_type = BHND_PORT_DEVICE,
},
.res = { SYS_RES_MEMORY, PCIR_BAR(0) }
},
/* bar0+0x1E00: pci core (SSB CFG registers) */
{
.win_type = BHNDB_REGWIN_T_CORE,
.win_offset = BHNDB_PCI_V0_BAR0_PCISB_OFFSET ,
.win_size = BHNDB_PCI_V0_BAR0_PCISB_SIZE,
.d.core = {
.class = BHND_DEVCLASS_PCI,
.unit = 0,
.port = 0,
.region = 0,
.offset = BHNDB_PCI_V0_BAR0_PCISB_COREOFF,
.port_type = BHND_PORT_DEVICE
},
.res = { SYS_RES_MEMORY, PCIR_BAR(0) }
},
BHNDB_REGWIN_TABLE_END
},

View File

@ -40,7 +40,8 @@
* [offset+ size] type description
* [0x0000+0x1000] dynamic mapped backplane address space (window 0).
* [0x1000+0x0800] fixed SPROM shadow
* [0x1800+0x0800] fixed pci core registers
* [0x1800+0x0E00] fixed pci core device registers
* [0x1E00+0x0200] fixed pci core siba config registers
*
* == PCI_V1 ==
* Applies to:
@ -133,8 +134,11 @@
#define BHNDB_PCI_V0_BAR0_WIN0_SIZE 0x1000
#define BHNDB_PCI_V0_BAR0_SPROM_OFFSET 0x1000 /* bar0 + 4K accesses sprom shadow (in pci core) */
#define BHNDB_PCI_V0_BAR0_SPROM_SIZE 0x0800
#define BHNDB_PCI_V0_BAR0_PCIREG_OFFSET 0x1800 /* bar0 + 6K accesses pci core registers */
#define BHNDB_PCI_V0_BAR0_PCIREG_SIZE 0x0800
#define BHNDB_PCI_V0_BAR0_PCIREG_OFFSET 0x1800 /* bar0 + 6K accesses pci core registers (not including SSB CFG registers) */
#define BHNDB_PCI_V0_BAR0_PCIREG_SIZE 0x0E00
#define BHNDB_PCI_V0_BAR0_PCISB_OFFSET 0x1E00 /* bar0 + 7.5K accesses pci core's SSB CFG register blocks */
#define BHNDB_PCI_V0_BAR0_PCISB_SIZE 0x0200
#define BHNDB_PCI_V0_BAR0_PCISB_COREOFF 0xE00 /* mapped offset relative to the core base address */
/* PCI_V1 */
#define BHNDB_PCI_V1_BAR0_WIN0_CONTROL 0x80 /* backplane address space accessed by BAR0/WIN0 */

View File

@ -70,6 +70,7 @@ 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);
int bhndb_find_resource_limits(
@ -93,6 +94,10 @@ struct bhndb_intr_handler *bhndb_find_intr_handler(
struct bhndb_resources *br,
void *cookiep);
bool bhndb_has_static_region_mapping(
struct bhndb_resources *br,
bhnd_addr_t addr, bhnd_size_t size);
struct bhndb_region *bhndb_find_resource_region(
struct bhndb_resources *br,
bhnd_addr_t addr, bhnd_size_t size);
@ -120,10 +125,24 @@ int bhndb_dw_set_addr(device_t dev,
struct bhndb_dw_alloc *dwa,
bus_addr_t addr, bus_size_t size);
struct bhndb_dw_alloc *bhndb_dw_steal(struct bhndb_resources *br,
bus_addr_t *saved);
void bhndb_dw_return_stolen(device_t dev,
struct bhndb_resources *br,
struct bhndb_dw_alloc *dwa,
bus_addr_t saved);
const struct bhndb_hw_priority *bhndb_hw_priority_find_core(
const struct bhndb_hw_priority *table,
struct bhnd_core_info *core);
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);
/**
* Dynamic register window allocation reference.
@ -152,6 +171,7 @@ struct bhndb_region {
bhnd_addr_t addr; /**< start of mapped range */
bhnd_size_t size; /**< size of mapped range */
bhndb_priority_t priority; /**< direct resource allocation priority */
uint32_t alloc_flags; /**< resource allocation flags (@see bhndb_alloc_flags) */
const struct bhndb_regwin *static_regwin; /**< fixed mapping regwin, if any */
STAILQ_ENTRY(bhndb_region) link;
@ -185,6 +205,7 @@ struct bhndb_resources {
STAILQ_HEAD(, bhndb_region) bus_regions; /**< bus region descriptors */
struct mtx dw_steal_mtx; /**< spinlock must be held when stealing a dynamic window allocation */
struct bhndb_dw_alloc *dw_alloc; /**< dynamic window allocation records */
size_t dwa_count; /**< number of dynamic windows available. */
bitstr_t *dwa_freelist; /**< dynamic window free list */

View File

@ -291,7 +291,10 @@ bhndb_alloc_resources(device_t dev, device_t parent_dev,
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);
/* Initialize host address space resource manager. */
r->ht_mem_rman.rm_start = 0;
r->ht_mem_rman.rm_end = ~0;
@ -492,6 +495,8 @@ bhndb_alloc_resources(device_t dev, device_t parent_dev,
if (r->res != NULL)
bhndb_release_host_resources(r->res);
mtx_destroy(&r->dw_steal_mtx);
free(r, M_BHND);
return (NULL);
@ -626,6 +631,10 @@ bhndb_free_resources(struct bhndb_resources *br)
free(br->dw_alloc, M_BHND);
free(br->dwa_freelist, M_BHND);
mtx_destroy(&br->dw_steal_mtx);
free(br, M_BHND);
}
/**
@ -1054,6 +1063,7 @@ bhndb_find_resource_limits(struct bhndb_resources *br, int type,
* @param size The size of this region.
* @param priority The resource priority to be assigned to allocations
* made within this bus region.
* @param alloc_flags resource allocation flags (@see bhndb_alloc_flags)
* @param static_regwin If available, a static register window mapping this
* bus region entry. If not available, NULL.
*
@ -1062,7 +1072,7 @@ bhndb_find_resource_limits(struct bhndb_resources *br, int type,
*/
int
bhndb_add_resource_region(struct bhndb_resources *br, bhnd_addr_t addr,
bhnd_size_t size, bhndb_priority_t priority,
bhnd_size_t size, bhndb_priority_t priority, uint32_t alloc_flags,
const struct bhndb_regwin *static_regwin)
{
struct bhndb_region *reg;
@ -1076,6 +1086,7 @@ bhndb_add_resource_region(struct bhndb_resources *br, bhnd_addr_t addr,
.addr = addr,
.size = size,
.priority = priority,
.alloc_flags = alloc_flags,
.static_regwin = static_regwin
};
@ -1084,6 +1095,39 @@ bhndb_add_resource_region(struct bhndb_resources *br, bhnd_addr_t addr,
return (0);
}
/**
* Return true if a mapping of @p size bytes at @p addr is provided by either
* one contiguous bus region, or by multiple discontiguous regions.
*
* @param br The resource state to query.
* @param addr The requested starting address.
* @param size The requested size.
*/
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) {
/* Must be backed by a static register window */
if (region->static_regwin == NULL)
return (false);
/* Adjust the search offset */
r_addr += region->size;
/* Have we traversed a complete (if discontiguous) mapping? */
if (r_addr == addr + size)
return (true);
}
/* No complete mapping found */
return (false);
}
/**
* Find the bus region that maps @p size bytes at @p addr.
@ -1302,7 +1346,7 @@ bhndb_dw_set_addr(device_t dev, struct bhndb_resources *br,
rw = dwa->win;
KASSERT(bhndb_dw_is_free(br, dwa),
KASSERT(bhndb_dw_is_free(br, dwa) || mtx_owned(&br->dw_steal_mtx),
("attempting to set the target address on an in-use window"));
/* Page-align the target address */
@ -1323,6 +1367,74 @@ bhndb_dw_set_addr(device_t dev, struct bhndb_resources *br,
return (0);
}
/**
* Steal an in-use allocation record from @p br, returning the record's current
* target in @p saved on success.
*
* This function acquires a mutex and disables interrupts; callers should
* avoid holding a stolen window longer than required to issue an I/O
* request.
*
* A successful call to bhndb_dw_steal() must be balanced with a call to
* bhndb_dw_return_stolen().
*
* @param br The resource state from which a window should be stolen.
* @param saved The stolen window's saved target address.
*
* @retval non-NULL success
* @retval NULL no dynamic window regions are defined.
*/
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"));
/* Nothing to steal from? */
if (br->dwa_count == 0)
return (NULL);
/*
* Acquire our steal spinlock; this will be released in
* bhndb_dw_return_stolen().
*
* Acquiring also disables interrupts, which is required when one is
* stealing an in-use existing register window.
*/
mtx_lock_spin(&br->dw_steal_mtx);
dw_stolen = &br->dw_alloc[0];
*saved = dw_stolen->target;
return (dw_stolen);
}
/**
* Return an allocation record previously stolen using bhndb_dw_steal().
*
* @param dev The device on which to issue a BHNDB_SET_WINDOW_ADDR() request.
* @param br The resource state owning @p dwa.
* @param dwa The allocation record to be returned.
* @param saved The original target address provided by bhndb_dw_steal().
*/
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);
}
/**
* Return the count of @p type register windows in @p table.
*
@ -1380,18 +1492,24 @@ bhndb_regwin_find_type(const struct bhndb_regwin *table,
* @param port_type The required port type.
* @param port The required port.
* @param region The required region.
* @param offset The required readable core register block offset.
* @param min_size The required minimum readable size at @p offset.
*
* @retval bhndb_regwin The first matching window.
* @retval NULL If no matching window was found.
*/
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)
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;
/* Match on core, port, and region attributes */
if (rw->win_type != BHNDB_REGWIN_T_CORE)
continue;
@ -1410,6 +1528,19 @@ bhndb_regwin_find_core(const struct bhndb_regwin *table, bhnd_devclass_t class,
if (rw->d.core.region != region)
continue;
/* Verify that the requested range is mapped within
* this register window */
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);
}
@ -1429,7 +1560,8 @@ bhndb_regwin_find_core(const struct bhndb_regwin *table, bhnd_devclass_t class,
* @param port_type The required port type.
* @param port The required port.
* @param region The required region.
* @param min_size The minimum window size.
* @param offset The required readable core register block offset.
* @param min_size The required minimum readable size at @p offset.
*
* @retval bhndb_regwin The first matching window.
* @retval NULL If no matching window was found.
@ -1437,13 +1569,13 @@ bhndb_regwin_find_core(const struct bhndb_regwin *table, bhnd_devclass_t class,
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 min_size)
u_int region, bus_size_t offset, bus_size_t min_size)
{
const struct bhndb_regwin *rw;
/* Prefer a fixed core mapping */
rw = bhndb_regwin_find_core(table, class, unit, port_type,
port, region);
port, region, offset, min_size);
if (rw != NULL)
return (rw);
@ -1499,3 +1631,42 @@ bhndb_hw_priority_find_core(const struct bhndb_hw_priority *table,
/* not found */
return (NULL);
}
/**
* Search for a port resource priority descriptor in @p table.
*
* @param table The table to search.
* @param core The core to match against @p table.
* @param port_type The required port type.
* @param port The required port.
* @param region The required region.
*/
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);
}
/* not found */
return (NULL);
}

View File

@ -118,13 +118,15 @@ 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);
u_int region, bus_size_t offset,
bus_size_t min_size);
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 min_size);
u_int region, bus_size_t offset,
bus_size_t min_size);
bool bhndb_regwin_match_core(
const struct bhndb_regwin *regw,