bhnd(4): implement MIPS and PCI(e) interrupt support

On BHND MIPS SoCs, this replaces the use of hard-coded MIPS IRQ#s in the
common bhnd(4) core drivers; we now register an INTRNG child PIC that
handles routing of backplane interrupt vectors via the MIPS core.

On BHND PCI devices, backplane interrupt vectors are now routed to the
PCI/PCIe host bridge core when bus_setup_intr() is called, where they are
dispatched by the PCI core via a host interrupt (e.g. INTx/MSI).

The bhndb(4) bridge driver tracks registered interrupt handlers for the
bridged bhnd(4) devices and manages backplane interrupt routing, while
delegating actual bus interrupt setup/teardown to the parent bus on behalf
of the bridged cores.

Approved by:	adrian (mentor, implicit)
Sponsored by:	The FreeBSD Foundation
Differential Revision:	https://reviews.freebsd.org/D12518
This commit is contained in:
landonf 2017-11-21 23:15:20 +00:00
parent ff59e3424a
commit 7bdcca7896
45 changed files with 3971 additions and 837 deletions

View File

@ -1,7 +1,11 @@
/*-
* Copyright (c) 2015 Landon Fuller <landon@landonf.org>
* Copyright (c) 2015-2016 Landon Fuller <landon@landonf.org>
* Copyright (c) 2017 The FreeBSD Foundation
* All rights reserved.
*
* Portions of this software were developed by Landon Fuller
* under sponsorship from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
@ -124,7 +128,7 @@ bcma_child_deleted(device_t dev, device_t child)
/* Free bcma device info */
if ((dinfo = device_get_ivars(child)) != NULL)
bcma_free_dinfo(dev, dinfo);
bcma_free_dinfo(dev, child, dinfo);
device_set_ivars(child, NULL);
}
@ -613,67 +617,47 @@ bcma_get_region_addr(device_t dev, device_t child, bhnd_port_type port_type,
/**
* Default bcma(4) bus driver implementation of BHND_BUS_GET_INTR_COUNT().
*
* This implementation consults @p child's agent register block,
* returning the number of interrupt output lines routed to @p child.
*/
int
u_int
bcma_get_intr_count(device_t dev, device_t child)
{
struct bcma_devinfo *dinfo;
uint32_t dmpcfg, oobw;
/* delegate non-bus-attached devices to our parent */
if (device_get_parent(child) != dev)
return (BHND_BUS_GET_INTR_COUNT(device_get_parent(dev), child));
dinfo = device_get_ivars(child);
/* Agent block must be mapped */
if (dinfo->res_agent == NULL)
return (0);
/* Agent must support OOB */
dmpcfg = bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_CONFIG);
if (!BCMA_DMP_GET_FLAG(dmpcfg, BCMA_DMP_CFG_OOB))
return (0);
/* Return OOB width as interrupt count */
oobw = bhnd_bus_read_4(dinfo->res_agent,
BCMA_DMP_OOB_OUTWIDTH(BCMA_OOB_BANK_INTR));
if (oobw > BCMA_OOB_NUM_SEL) {
device_printf(dev, "ignoring invalid OOBOUTWIDTH for core %u: "
"%#x\n", BCMA_DINFO_COREIDX(dinfo), oobw);
return (0);
}
return (oobw);
return (dinfo->num_intrs);
}
/**
* Default bcma(4) bus driver implementation of BHND_BUS_GET_CORE_IVEC().
*
* This implementation consults @p child's agent register block,
* returning the interrupt output line routed to @p child, at OOB selector
* @p intr.
* Default bcma(4) bus driver implementation of BHND_BUS_GET_INTR_IVEC().
*/
int
bcma_get_core_ivec(device_t dev, device_t child, u_int intr, uint32_t *ivec)
bcma_get_intr_ivec(device_t dev, device_t child, u_int intr, u_int *ivec)
{
struct bcma_devinfo *dinfo;
uint32_t oobsel;
struct bcma_intr *desc;
/* delegate non-bus-attached devices to our parent */
if (device_get_parent(child) != dev) {
return (BHND_BUS_GET_INTR_IVEC(device_get_parent(dev), child,
intr, ivec));
}
dinfo = device_get_ivars(child);
/* Interrupt ID must be valid. */
if (intr >= bcma_get_intr_count(dev, child))
return (ENXIO);
/* Fetch OOBSEL busline value */
KASSERT(dinfo->res_agent != NULL, ("missing agent registers"));
oobsel = bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_OOBSELOUT(
BCMA_OOB_BANK_INTR, intr));
*ivec = (oobsel >> BCMA_DMP_OOBSEL_SHIFT(intr)) &
BCMA_DMP_OOBSEL_BUSLINE_MASK;
STAILQ_FOREACH(desc, &dinfo->intrs, i_link) {
if (desc->i_sel == intr) {
*ivec = desc->i_busline;
return (0);
}
}
/* Not found */
return (ENXIO);
}
/**
* Scan the device enumeration ROM table, adding all valid discovered cores to
@ -707,8 +691,6 @@ bcma_add_children(device_t bus)
/* Add all cores. */
bcma_erom = (struct bcma_erom *)erom;
while ((error = bcma_erom_next_corecfg(bcma_erom, &corecfg)) == 0) {
int nintr;
/* Add the child device */
child = BUS_ADD_CHILD(bus, 0, NULL, -1);
if (child == NULL) {
@ -718,27 +700,12 @@ bcma_add_children(device_t bus)
/* Initialize device ivars */
dinfo = device_get_ivars(child);
if ((error = bcma_init_dinfo(bus, dinfo, corecfg)))
if ((error = bcma_init_dinfo(bus, child, dinfo, corecfg)))
goto cleanup;
/* The dinfo instance now owns the corecfg value */
corecfg = NULL;
/* Allocate device's agent registers, if any */
if ((error = bcma_dinfo_alloc_agent(bus, child, dinfo)))
goto cleanup;
/* Assign interrupts */
nintr = bhnd_get_intr_count(child);
for (int rid = 0; rid < nintr; rid++) {
error = BHND_BUS_ASSIGN_INTR(bus, child, rid);
if (error) {
device_printf(bus, "failed to assign interrupt "
"%d to core %u: %d\n", rid,
BCMA_DINFO_COREIDX(dinfo), error);
}
}
/* If pins are floating or the hardware is otherwise
* unpopulated, the device shouldn't be used. */
if (bhnd_is_hw_disabled(child))
@ -794,7 +761,7 @@ static device_method_t bcma_methods[] = {
DEVMETHOD(bhnd_bus_decode_port_rid, bcma_decode_port_rid),
DEVMETHOD(bhnd_bus_get_region_addr, bcma_get_region_addr),
DEVMETHOD(bhnd_bus_get_intr_count, bcma_get_intr_count),
DEVMETHOD(bhnd_bus_get_core_ivec, bcma_get_core_ivec),
DEVMETHOD(bhnd_bus_get_intr_ivec, bcma_get_intr_ivec),
DEVMETHOD_END
};

View File

@ -1,7 +1,11 @@
/*-
* Copyright (c) 2015 Landon Fuller <landon@landonf.org>
* Copyright (c) 2015-2016 Landon Fuller <landon@landonf.org>
* Copyright (c) 2017 The FreeBSD Foundation
* All rights reserved.
*
* Portions of this software were developed by Landon Fuller
* under sponsorship from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
@ -154,7 +158,7 @@ bcma_corecfg_get_port_list(struct bcma_corecfg *cfg, bhnd_port_type type)
* @param ports The set of ports to be enumerated
*/
static void
bcma_dinfo_init_resource_info(device_t bus, struct bcma_devinfo *dinfo,
bcma_dinfo_init_port_resource_info(device_t bus, struct bcma_devinfo *dinfo,
struct bcma_sport_list *ports)
{
struct bcma_map *map;
@ -193,67 +197,9 @@ bcma_dinfo_init_resource_info(device_t bus, struct bcma_devinfo *dinfo,
}
/**
* Allocate and return a new empty device info structure.
*
* @param bus The requesting bus device.
*
* @retval NULL if allocation failed.
*/
struct bcma_devinfo *
bcma_alloc_dinfo(device_t bus)
{
struct bcma_devinfo *dinfo;
dinfo = malloc(sizeof(struct bcma_devinfo), M_BHND, M_NOWAIT|M_ZERO);
if (dinfo == NULL)
return (NULL);
dinfo->corecfg = NULL;
dinfo->res_agent = NULL;
dinfo->rid_agent = -1;
resource_list_init(&dinfo->resources);
return (dinfo);
}
/**
* Initialize a device info structure previously allocated via
* bcma_alloc_dinfo, assuming ownership of the provided core
* configuration.
*
* @param bus The requesting bus device.
* @param dinfo The device info instance.
* @param corecfg Device core configuration; ownership of this value
* will be assumed by @p dinfo.
*
* @retval 0 success
* @retval non-zero initialization failed.
*/
int
bcma_init_dinfo(device_t bus, struct bcma_devinfo *dinfo,
struct bcma_corecfg *corecfg)
{
KASSERT(dinfo->corecfg == NULL, ("dinfo previously initialized"));
/* Save core configuration value */
dinfo->corecfg = corecfg;
/* The device ports must always be initialized first to ensure that
* rid 0 maps to the first device port */
bcma_dinfo_init_resource_info(bus, dinfo, &corecfg->dev_ports);
bcma_dinfo_init_resource_info(bus, dinfo, &corecfg->bridge_ports);
bcma_dinfo_init_resource_info(bus, dinfo, &corecfg->wrapper_ports);
return (0);
}
/**
* Allocate the per-core agent register block for a device info structure
* previous initialized via bcma_init_dinfo().
* Allocate the per-core agent register block for a device info structure.
*
* If an agent0.0 region is not defined on @p dinfo, the device info
* agent resource is set to NULL and 0 is returned.
@ -265,8 +211,8 @@ bcma_init_dinfo(device_t bus, struct bcma_devinfo *dinfo,
* @retval 0 success
* @retval non-zero resource allocation failed.
*/
int
bcma_dinfo_alloc_agent(device_t bus, device_t child, struct bcma_devinfo *dinfo)
static int
bcma_dinfo_init_agent(device_t bus, device_t child, struct bcma_devinfo *dinfo)
{
bhnd_addr_t addr;
bhnd_size_t size;
@ -296,7 +242,7 @@ bcma_dinfo_alloc_agent(device_t bus, device_t child, struct bcma_devinfo *dinfo)
dinfo->rid_agent = BCMA_AGENT_RID(dinfo);
dinfo->res_agent = BHND_BUS_ALLOC_RESOURCE(bus, bus, SYS_RES_MEMORY,
&dinfo->rid_agent, r_start, r_end, r_count, RF_ACTIVE);
&dinfo->rid_agent, r_start, r_end, r_count, RF_ACTIVE|RF_SHAREABLE);
if (dinfo->res_agent == NULL) {
device_printf(bus, "failed allocating agent register block for "
"core %u\n", BCMA_DINFO_COREIDX(dinfo));
@ -306,6 +252,172 @@ bcma_dinfo_alloc_agent(device_t bus, device_t child, struct bcma_devinfo *dinfo)
return (0);
}
/**
* Populate the list of interrupts for a device info structure
* previously initialized via bcma_dinfo_alloc_agent().
*
* If an agent0.0 region is not mapped on @p dinfo, the OOB interrupt bank is
* assumed to be unavailable and 0 is returned.
*
* @param bus The requesting bus device.
* @param dinfo The device info instance to be initialized.
*/
static int
bcma_dinfo_init_intrs(device_t bus, device_t child,
struct bcma_devinfo *dinfo)
{
uint32_t dmpcfg, oobw;
/* Agent block must be mapped */
if (dinfo->res_agent == NULL)
return (0);
/* Agent must support OOB */
dmpcfg = bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_CONFIG);
if (!BCMA_DMP_GET_FLAG(dmpcfg, BCMA_DMP_CFG_OOB))
return (0);
/* Fetch width of the OOB interrupt bank */
oobw = bhnd_bus_read_4(dinfo->res_agent,
BCMA_DMP_OOB_OUTWIDTH(BCMA_OOB_BANK_INTR));
if (oobw > BCMA_OOB_NUM_SEL) {
device_printf(bus, "ignoring invalid OOBOUTWIDTH for core %u: "
"%#x\n", BCMA_DINFO_COREIDX(dinfo), oobw);
return (0);
}
/* Fetch OOBSEL busline values and populate list of interrupt
* descriptors */
for (uint32_t sel = 0; sel < oobw; sel++) {
struct bcma_intr *intr;
uint32_t selout;
uint8_t line;
if (dinfo->num_intrs == UINT_MAX)
return (ENOMEM);
selout = bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_OOBSELOUT(
BCMA_OOB_BANK_INTR, sel));
line = (selout >> BCMA_DMP_OOBSEL_SHIFT(sel)) &
BCMA_DMP_OOBSEL_BUSLINE_MASK;
intr = bcma_alloc_intr(BCMA_OOB_BANK_INTR, sel, line);
if (intr == NULL) {
device_printf(bus, "failed allocating interrupt "
"descriptor %#x for core %u\n", sel,
BCMA_DINFO_COREIDX(dinfo));
return (ENOMEM);
}
STAILQ_INSERT_HEAD(&dinfo->intrs, intr, i_link);
dinfo->num_intrs++;
}
return (0);
}
/**
* Allocate and return a new empty device info structure.
*
* @param bus The requesting bus device.
*
* @retval NULL if allocation failed.
*/
struct bcma_devinfo *
bcma_alloc_dinfo(device_t bus)
{
struct bcma_devinfo *dinfo;
dinfo = malloc(sizeof(struct bcma_devinfo), M_BHND, M_NOWAIT|M_ZERO);
if (dinfo == NULL)
return (NULL);
dinfo->corecfg = NULL;
dinfo->res_agent = NULL;
dinfo->rid_agent = -1;
STAILQ_INIT(&dinfo->intrs);
dinfo->num_intrs = 0;
resource_list_init(&dinfo->resources);
return (dinfo);
}
/**
* Initialize a device info structure previously allocated via
* bcma_alloc_dinfo, assuming ownership of the provided core
* configuration.
*
* @param bus The requesting bus device.
* @param child The bcma child device.
* @param dinfo The device info associated with @p child
* @param corecfg Device core configuration; ownership of this value
* will be assumed by @p dinfo.
*
* @retval 0 success
* @retval non-zero initialization failed.
*/
int
bcma_init_dinfo(device_t bus, device_t child, struct bcma_devinfo *dinfo,
struct bcma_corecfg *corecfg)
{
struct bcma_intr *intr;
int error;
KASSERT(dinfo->corecfg == NULL, ("dinfo previously initialized"));
/* Save core configuration value */
dinfo->corecfg = corecfg;
/* The device ports must always be initialized first to ensure that
* rid 0 maps to the first device port */
bcma_dinfo_init_port_resource_info(bus, dinfo, &corecfg->dev_ports);
bcma_dinfo_init_port_resource_info(bus, dinfo, &corecfg->bridge_ports);
bcma_dinfo_init_port_resource_info(bus, dinfo, &corecfg->wrapper_ports);
/* Now that we've defined the port resources, we can map the device's
* agent registers (if any) */
if ((error = bcma_dinfo_init_agent(bus, child, dinfo)))
goto failed;
/* With agent registers mapped, we can populate the device's interrupt
* descriptors */
if ((error = bcma_dinfo_init_intrs(bus, child, dinfo)))
goto failed;
/* Finally, map the interrupt descriptors */
STAILQ_FOREACH(intr, &dinfo->intrs, i_link) {
/* Already mapped? */
if (intr->i_mapped)
continue;
/* Map the interrupt */
error = BHND_BUS_MAP_INTR(bus, child, intr->i_sel,
&intr->i_irq);
if (error) {
device_printf(bus, "failed mapping interrupt line %u "
"for core %u: %d\n", intr->i_sel,
BCMA_DINFO_COREIDX(dinfo), error);
goto failed;
}
intr->i_mapped = true;
/* Add to resource list */
intr->i_rid = resource_list_add_next(&dinfo->resources,
SYS_RES_IRQ, intr->i_irq, intr->i_irq, 1);
}
return (0);
failed:
/* Owned by the caller on failure */
dinfo->corecfg = NULL;
return (error);
}
/**
* Deallocate the given device info structure and any associated resources.
@ -314,8 +426,10 @@ bcma_dinfo_alloc_agent(device_t bus, device_t child, struct bcma_devinfo *dinfo)
* @param dinfo Device info to be deallocated.
*/
void
bcma_free_dinfo(device_t bus, struct bcma_devinfo *dinfo)
bcma_free_dinfo(device_t bus, device_t child, struct bcma_devinfo *dinfo)
{
struct bcma_intr *intr, *inext;
resource_list_free(&dinfo->resources);
if (dinfo->corecfg != NULL)
@ -327,10 +441,70 @@ bcma_free_dinfo(device_t bus, struct bcma_devinfo *dinfo)
dinfo->res_agent);
}
/* Clean up interrupt descriptors */
STAILQ_FOREACH_SAFE(intr, &dinfo->intrs, i_link, inext) {
STAILQ_REMOVE(&dinfo->intrs, intr, bcma_intr, i_link);
/* Release our IRQ mapping */
if (intr->i_mapped) {
BHND_BUS_UNMAP_INTR(bus, child, intr->i_irq);
intr->i_mapped = false;
}
bcma_free_intr(intr);
}
free(dinfo, M_BHND);
}
/**
* Allocate and initialize a new interrupt descriptor.
*
* @param bank OOB bank.
* @param sel OOB selector.
* @param line OOB bus line.
*/
struct bcma_intr *
bcma_alloc_intr(uint8_t bank, uint8_t sel, uint8_t line)
{
struct bcma_intr *intr;
if (bank >= BCMA_OOB_NUM_BANKS)
return (NULL);
if (sel >= BCMA_OOB_NUM_SEL)
return (NULL);
if (line >= BCMA_OOB_NUM_BUSLINES)
return (NULL);
intr = malloc(sizeof(*intr), M_BHND, M_NOWAIT);
if (intr == NULL)
return (NULL);
intr->i_bank = bank;
intr->i_sel = sel;
intr->i_busline = line;
intr->i_mapped = false;
intr->i_irq = 0;
return (intr);
}
/**
* Deallocate all resources associated with the given interrupt descriptor.
*
* @param intr Interrupt descriptor to be deallocated.
*/
void
bcma_free_intr(struct bcma_intr *intr)
{
KASSERT(!intr->i_mapped, ("interrupt %u still mapped", intr->i_sel));
free(intr, M_BHND);
}
/**
* Allocate and initialize new slave port descriptor.
*

View File

@ -1,7 +1,11 @@
/*-
* Copyright (c) 2015 Landon Fuller <landon@landonf.org>
* Copyright (c) 2015-2016 Landon Fuller <landon@landonf.org>
* Copyright (c) 2017 The FreeBSD Foundation
* All rights reserved.
*
* Portions of this software were developed by Landon Fuller
* under sponsorship from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
@ -67,6 +71,7 @@ typedef u_int bcma_rmid_t;
struct bcma_devinfo;
struct bcma_corecfg;
struct bcma_intr;
struct bcma_map;
struct bcma_mport;
struct bcma_sport;
@ -74,8 +79,8 @@ struct bcma_sport;
int bcma_probe(device_t dev);
int bcma_attach(device_t dev);
int bcma_detach(device_t dev);
int bcma_get_intr_count(device_t dev, device_t child);
int bcma_get_core_ivec(device_t dev, device_t child,
u_int bcma_get_intr_count(device_t dev, device_t child);
int bcma_get_intr_ivec(device_t dev, device_t child,
u_int intr, uint32_t *ivec);
int bcma_add_children(device_t bus);
@ -84,18 +89,20 @@ struct bcma_sport_list *bcma_corecfg_get_port_list(struct bcma_corecfg *cfg,
bhnd_port_type type);
struct bcma_devinfo *bcma_alloc_dinfo(device_t bus);
int bcma_init_dinfo(device_t bus,
int bcma_init_dinfo(device_t bus, device_t child,
struct bcma_devinfo *dinfo,
struct bcma_corecfg *corecfg);
int bcma_dinfo_alloc_agent(device_t bus, device_t child,
struct bcma_devinfo *dinfo);
void bcma_free_dinfo(device_t bus,
void bcma_free_dinfo(device_t bus, device_t child,
struct bcma_devinfo *dinfo);
struct bcma_corecfg *bcma_alloc_corecfg(u_int core_index, int core_unit,
uint16_t vendor, uint16_t device, uint8_t hwrev);
void bcma_free_corecfg(struct bcma_corecfg *corecfg);
struct bcma_intr *bcma_alloc_intr(uint8_t bank, uint8_t sel,
uint8_t line);
void bcma_free_intr(struct bcma_intr *intr);
struct bcma_sport *bcma_alloc_sport(bcma_pid_t port_num, bhnd_port_type port_type);
void bcma_free_sport(struct bcma_sport *sport);
@ -121,6 +128,18 @@ struct bcma_map {
STAILQ_ENTRY(bcma_map) m_link;
};
/** BCMA interrupt descriptor */
struct bcma_intr {
uint8_t i_bank; /**< OOB bank (see BCMA_OOB_BANK[A-D]) */
uint8_t i_sel; /**< OOB selector (0-7) */
uint8_t i_busline; /**< OOB bus line assigned to this selector */
bool i_mapped; /**< if an irq has been mapped for this selector */
int i_rid; /**< bus resource id, or -1 */
rman_res_t i_irq; /**< the mapped bus irq, if any */
STAILQ_ENTRY(bcma_intr) i_link;
};
/** BCMA slave port descriptor */
struct bcma_sport {
bcma_pid_t sp_num; /**< slave port number (core-unique) */
@ -132,6 +151,7 @@ struct bcma_sport {
};
STAILQ_HEAD(bcma_mport_list, bcma_mport);
STAILQ_HEAD(bcma_intr_list, bcma_intr);
STAILQ_HEAD(bcma_sport_list, bcma_sport);
/** BCMA IP core/block configuration */
@ -162,6 +182,9 @@ struct bcma_devinfo {
* all bcma(4) cores have or require an agent. */
int rid_agent; /**< Agent resource ID, or -1 */
u_int num_intrs; /**< number of interrupt descriptors. */
struct bcma_intr_list intrs; /**< interrupt descriptors */
struct bhnd_core_pmu_info *pmu_info; /**< Bus-managed PMU state, or NULL */
};

View File

@ -814,6 +814,22 @@ bhnd_generic_resume_child(device_t dev, device_t child)
return bus_generic_resume_child(dev, child);
}
/**
* Default bhnd(4) bus driver implementation of BUS_SETUP_INTR().
*
* This implementation of BUS_SETUP_INTR() will delegate interrupt setup
* to the parent of @p dev, if any.
*/
int
bhnd_generic_setup_intr(device_t dev, device_t child, struct resource *irq,
int flags, driver_filter_t *filter, driver_intr_t *intr, void *arg,
void **cookiep)
{
return (bus_generic_setup_intr(dev, child, irq, flags, filter, intr,
arg, cookiep));
}
/*
* Delegate all indirect I/O to the parent device. When inherited by
* non-bridged bus implementations, resources will never be marked as
@ -917,7 +933,7 @@ static device_method_t bhnd_methods[] = {
DEVMETHOD(bus_activate_resource, bus_generic_activate_resource),
DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource),
DEVMETHOD(bus_setup_intr, bus_generic_setup_intr),
DEVMETHOD(bus_setup_intr, bhnd_generic_setup_intr),
DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr),
DEVMETHOD(bus_config_intr, bus_generic_config_intr),
DEVMETHOD(bus_bind_intr, bus_generic_bind_intr),

View File

@ -250,10 +250,10 @@ struct bhnd_device_quirk {
{{ BHND_MATCH_CORE_REV(_rev) }, (_flags) }
#define BHND_CHIP_QUIRK(_chip, _rev, _flags) \
{{ BHND_CHIP_IR(BCM ## _chip, _rev) }, (_flags) }
{{ BHND_MATCH_CHIP_IR(BCM ## _chip, _rev) }, (_flags) }
#define BHND_PKG_QUIRK(_chip, _pkg, _flags) \
{{ BHND_CHIP_IP(BCM ## _chip, BCM ## _chip ## _pkg) }, (_flags) }
{{ BHND_MATCH_CHIP_IP(BCM ## _chip, BCM ## _chip ## _pkg) }, (_flags) }
#define BHND_BOARD_QUIRK(_board, _flags) \
{{ BHND_MATCH_BOARD_TYPE(_board) }, \
@ -528,8 +528,8 @@ int bhnd_bus_generic_activate_resource (device_t dev,
int bhnd_bus_generic_deactivate_resource (device_t dev,
device_t child, int type, int rid,
struct bhnd_resource *r);
bhnd_attach_type bhnd_bus_generic_get_attach_type(device_t dev,
device_t child);
uintptr_t bhnd_bus_generic_get_intr_domain(device_t dev,
device_t child, bool self);
/**
* Return the bhnd(4) bus driver's device enumeration parser class
@ -865,25 +865,22 @@ bhnd_read_board_info(device_t dev, struct bhnd_board_info *info)
}
/**
* Return the number of interrupts to be assigned to @p child via
* BHND_BUS_ASSIGN_INTR().
* Return the number of interrupt lines assigned to @p dev.
*
* @param dev A bhnd bus child device.
*/
static inline int
static inline u_int
bhnd_get_intr_count(device_t dev)
{
return (BHND_BUS_GET_INTR_COUNT(device_get_parent(dev), dev));
}
/**
* Return the backplane interrupt vector corresponding to @p dev's given
* @p intr number.
* Get the backplane interrupt vector of the @p intr line attached to @p dev.
*
* @param dev A bhnd bus child device.
* @param intr The interrupt number being queried. This is equivalent to the
* bus resource ID for the interrupt.
* @param[out] ivec On success, the assigned hardware interrupt vector be
* @param intr The index of the interrupt line being queried.
* @param[out] ivec On success, the assigned hardware interrupt vector will be
* written to this pointer.
*
* On bcma(4) devices, this returns the OOB bus line assigned to the
@ -893,16 +890,50 @@ bhnd_get_intr_count(device_t dev)
* to the interrupt.
*
* @retval 0 success
* @retval ENXIO If @p intr exceeds the number of interrupts available
* to @p child.
* @retval ENXIO If @p intr exceeds the number of interrupt lines
* assigned to @p child.
*/
static inline int
bhnd_get_core_ivec(device_t dev, u_int intr, uint32_t *ivec)
bhnd_get_intr_ivec(device_t dev, u_int intr, u_int *ivec)
{
return (BHND_BUS_GET_CORE_IVEC(device_get_parent(dev), dev, intr,
return (BHND_BUS_GET_INTR_IVEC(device_get_parent(dev), dev, intr,
ivec));
}
/**
* Map the given @p intr to an IRQ number; until unmapped, this IRQ may be used
* to allocate a resource of type SYS_RES_IRQ.
*
* On success, the caller assumes ownership of the interrupt mapping, and
* is responsible for releasing the mapping via bhnd_unmap_intr().
*
* @param dev The requesting device.
* @param intr The interrupt being mapped.
* @param[out] irq On success, the bus interrupt value mapped for @p intr.
*
* @retval 0 If an interrupt was assigned.
* @retval non-zero If mapping an interrupt otherwise fails, a regular
* unix error code will be returned.
*/
static inline int
bhnd_map_intr(device_t dev, u_int intr, rman_res_t *irq)
{
return (BHND_BUS_MAP_INTR(device_get_parent(dev), dev, intr, irq));
}
/**
* Unmap an bus interrupt previously mapped via bhnd_map_intr().
*
* @param dev The requesting device.
* @param intr The interrupt number being unmapped. This is equivalent to the
* bus resource ID for the interrupt.
*/
static inline void
bhnd_unmap_intr(device_t dev, rman_res_t irq)
{
return (BHND_BUS_UNMAP_INTR(device_get_parent(dev), dev, irq));
}
/**
* Allocate and enable per-core PMU request handling for @p child.
*

View File

@ -141,25 +141,6 @@ CODE {
panic("bhnd_bus_read_boardinfo unimplemented");
}
static int
bhnd_bus_null_get_intr_count(device_t dev, device_t child)
{
panic("bhnd_bus_get_intr_count unimplemented");
}
static int
bhnd_bus_null_assign_intr(device_t dev, device_t child, int rid)
{
panic("bhnd_bus_assign_intr unimplemented");
}
static int
bhnd_bus_null_get_core_ivec(device_t dev, device_t child, u_int intr,
uint32_t *ivec)
{
panic("bhnd_bus_get_core_ivec unimplemented");
}
static void
bhnd_bus_null_child_added(device_t dev, device_t child)
{
@ -243,6 +224,39 @@ CODE {
panic("bhnd_bus_get_probe_order unimplemented");
}
static uintptr_t
bhnd_bus_null_get_intr_domain(device_t dev, device_t child, bool self)
{
/* Unsupported */
return (0);
}
static u_int
bhnd_bus_null_get_intr_count(device_t dev, device_t child)
{
return (0);
}
static int
bhnd_bus_null_get_intr_ivec(device_t dev, device_t child, u_int intr,
u_int *ivec)
{
panic("bhnd_bus_get_intr_ivec unimplemented");
}
static int
bhnd_bus_null_map_intr(device_t dev, device_t child, u_int intr,
rman_res_t *irq)
{
panic("bhnd_bus_map_intr unimplemented");
}
static int
bhnd_bus_null_unmap_intr(device_t dev, device_t child, rman_res_t irq)
{
panic("bhnd_bus_unmap_intr unimplemented");
}
static int
bhnd_bus_null_get_port_rid(device_t dev, device_t child,
bhnd_port_type port_type, u_int port, u_int region)
@ -487,77 +501,6 @@ METHOD int read_board_info {
struct bhnd_board_info *info;
} DEFAULT bhnd_bus_null_read_board_info;
/**
* Return the number of interrupts to be assigned to @p child via
* BHND_BUS_ASSIGN_INTR().
*
* @param dev The bhnd bus parent of @p child.
* @param child The bhnd device for which a count should be returned.
*
* @retval 0 If no interrupts should be assigned.
* @retval non-zero The count of interrupt resource IDs to be
* assigned, starting at rid 0.
*/
METHOD int get_intr_count {
device_t dev;
device_t child;
} DEFAULT bhnd_bus_null_get_intr_count;
/**
* Assign an interrupt to @p child via bus_set_resource().
*
* The default bus implementation of this method should assign backplane
* interrupt values to @p child.
*
* Bridge-attached bus implementations may instead override standard
* interconnect IRQ assignment, providing IRQs inherited from the parent bus.
*
* TODO: Once we can depend on INTRNG, investigate replacing this with a
* bridge-level interrupt controller.
*
* @param dev The bhnd bus parent of @p child.
* @param child The bhnd device to which an interrupt should be assigned.
* @param rid The interrupt resource ID to be assigned.
*
* @retval 0 If an interrupt was assigned.
* @retval non-zero If assigning an interrupt otherwise fails, a regular
* unix error code will be returned.
*/
METHOD int assign_intr {
device_t dev;
device_t child;
int rid;
} DEFAULT bhnd_bus_null_assign_intr;
/**
* Return the backplane interrupt vector corresponding to @p child's given
* @p intr number.
*
* @param dev The bhnd bus parent of @p child.
* @param child The bhnd device for which the assigned interrupt vector should
* be queried.
* @param intr The interrupt number being queried. This is equivalent to the
* bus resource ID for the interrupt.
* @param[out] ivec On success, the assigned hardware interrupt vector be
* written to this pointer.
*
* On bcma(4) devices, this returns the OOB bus line assigned to the
* interrupt.
*
* On siba(4) devices, this returns the target OCP slave flag number assigned
* to the interrupt.
*
* @retval 0 success
* @retval ENXIO If @p intr exceeds the number of interrupts available
* to @p child.
*/
METHOD int get_core_ivec {
device_t dev;
device_t child;
u_int intr;
uint32_t *ivec;
} DEFAULT bhnd_bus_null_get_core_ivec;
/**
* Notify a bhnd bus that a child was added.
*
@ -997,6 +940,106 @@ METHOD int deactivate_resource {
struct bhnd_resource *r;
} DEFAULT bhnd_bus_generic_deactivate_resource;
/**
* Return the interrupt domain.
*
* This globally unique value may be used as the interrupt controller 'xref'
* on targets that support INTRNG.
*
* @param dev The device whose child is being examined.
* @param child The child device.
* @parem self If true, return @p child's interrupt domain, rather than the
* domain in which @p child resides.
*
* On Non-OFW targets, this should either return:
* - The pointer address of a device that can uniquely identify @p child's
* interrupt domain (e.g., the bhnd bus' device_t address), or
* - 0 if unsupported by the bus.
*
* On OFW (including FDT) targets, this should return the @p child's iparent
* property's xref if @p self is false, the child's own node xref value if
* @p self is true, or 0 if no interrupt parent is found.
*/
METHOD uintptr_t get_intr_domain {
device_t dev;
device_t child;
bool self;
} DEFAULT bhnd_bus_null_get_intr_domain;
/**
* Return the number of interrupt lines assigned to @p child.
*
* @param dev The bhnd device whose child is being examined.
* @param child The child device.
*/
METHOD u_int get_intr_count {
device_t dev;
device_t child;
} DEFAULT bhnd_bus_null_get_intr_count;
/**
* Get the backplane interrupt vector of the @p intr line attached to @p child.
*
* @param dev The device whose child is being examined.
* @param child The child device.
* @param intr The index of the interrupt line being queried.
* @param[out] ivec On success, the assigned hardware interrupt vector will be
* written to this pointer.
*
* On bcma(4) devices, this returns the OOB bus line assigned to the
* interrupt.
*
* On siba(4) devices, this returns the target OCP slave flag number assigned
* to the interrupt.
*
* @retval 0 success
* @retval ENXIO If @p intr exceeds the number of interrupt lines
* assigned to @p child.
*/
METHOD int get_intr_ivec {
device_t dev;
device_t child;
u_int intr;
u_int *ivec;
} DEFAULT bhnd_bus_null_get_intr_ivec;
/**
* Map the given @p intr to an IRQ number; until unmapped, this IRQ may be used
* to allocate a resource of type SYS_RES_IRQ.
*
* On success, the caller assumes ownership of the interrupt mapping, and
* is responsible for releasing the mapping via BHND_BUS_UNMAP_INTR().
*
* @param dev The bhnd bus device.
* @param child The requesting child device.
* @param intr The interrupt being mapped.
* @param[out] irq On success, the bus interrupt value mapped for @p intr.
*
* @retval 0 If an interrupt was assigned.
* @retval non-zero If mapping an interrupt otherwise fails, a regular
* unix error code will be returned.
*/
METHOD int map_intr {
device_t dev;
device_t child;
u_int intr;
rman_res_t *irq;
} DEFAULT bhnd_bus_null_map_intr;
/**
* Unmap an bus interrupt previously mapped via BHND_BUS_MAP_INTR().
*
* @param dev The bhnd bus device.
* @param child The requesting child device.
* @param intr The interrupt number being unmapped. This is equivalent to the
* bus resource ID for the interrupt.
*/
METHOD void unmap_intr {
device_t dev;
device_t child;
rman_res_t irq;
} DEFAULT bhnd_bus_null_unmap_intr;
/**
* Return true if @p region_num is a valid region on @p port_num of
* @p type attached to @p child.

View File

@ -535,12 +535,16 @@
#define BHND_CHIPTYPE_UBUS 2 /**< ubus interconnect found in bcm63xx devices */
#define BHND_CHIPTYPE_BCMA_ALT 3 /**< bcma(4) interconnect */
/** Evaluates to true if @p _type uses a BCMA EROM table */
#define BHND_CHIPTYPE_HAS_EROM(_type) \
/** Evaluates to true if @p _type is a BCMA or BCMA-compatible interconenct */
#define BHND_CHIPTYPE_IS_BCMA_COMPATIBLE(_type) \
((_type) == BHND_CHIPTYPE_BCMA || \
(_type) == BHND_CHIPTYPE_BCMA_ALT || \
(_type) == BHND_CHIPTYPE_UBUS)
/** Evaluates to true if @p _type uses a BCMA EROM table */
#define BHND_CHIPTYPE_HAS_EROM(_type) \
BHND_CHIPTYPE_IS_BCMA_COMPATIBLE(_type)
/* Boardflags */
#define BHND_BFL_BTC2WIRE 0x00000001 /* old 2wire Bluetooth coexistence, OBSOLETE */
#define BHND_BFL_BTCOEX 0x00000001 /* Board supports BTCOEX */

View File

@ -154,7 +154,8 @@ struct bhnd_chip_match {
chip_id:1,
chip_rev:1,
chip_pkg:1,
flags_unused:5;
chip_type:1,
flags_unused:4;
} match;
} m;
@ -162,38 +163,46 @@ struct bhnd_chip_match {
uint16_t chip_id; /**< required chip id */
struct bhnd_hwrev_match chip_rev; /**< matching chip revisions */
uint8_t chip_pkg; /**< required package */
uint8_t chip_type; /**< required chip type (BHND_CHIPTYPE_*) */
};
#define _BHND_CHIP_MATCH_COPY(_src) \
_BHND_COPY_MATCH_FIELD(_src, chip_id), \
_BHND_COPY_MATCH_FIELD(_src, chip_rev), \
_BHND_COPY_MATCH_FIELD(_src, chip_pkg) \
_BHND_COPY_MATCH_FIELD(_src, chip_pkg), \
_BHND_COPY_MATCH_FIELD(_src, chip_type),\
/** Set the required chip ID within a bhnd match descriptor */
#define BHND_CHIP_ID(_cid) _BHND_SET_MATCH_FIELD(chip_id, \
#define BHND_MATCH_CHIP_ID(_cid) _BHND_SET_MATCH_FIELD(chip_id, \
BHND_CHIPID_ ## _cid)
/** Set the required chip revision range within a bhnd match descriptor */
#define BHND_CHIP_REV(_rev) _BHND_SET_MATCH_FIELD(chip_rev, \
#define BHND_MATCH_CHIP_REV(_rev) _BHND_SET_MATCH_FIELD(chip_rev, \
BHND_ ## _rev)
/** Set the required package ID within a bhnd match descriptor */
#define BHND_CHIP_PKG(_pkg) _BHND_SET_MATCH_FIELD(chip_pkg, \
#define BHND_MATCH_CHIP_PKG(_pkg) _BHND_SET_MATCH_FIELD(chip_pkg, \
BHND_PKGID_ ## _pkg)
/** Set the required chip type within a bhnd match descriptor */
#define BHND_MATCH_CHIP_TYPE(_type) _BHND_SET_MATCH_FIELD(chip_type, \
BHND_CHIPTYPE_ ## _type)
/** Set the required chip and package ID within a bhnd match descriptor */
#define BHND_CHIP_IP(_cid, _pkg) \
BHND_CHIP_ID(_cid), BHND_CHIP_PKG(_pkg)
#define BHND_MATCH_CHIP_IP(_cid, _pkg) \
BHND_MATCH_CHIP_ID(_cid), BHND_MATCH_CHIP_PKG(_pkg)
/** Set the required chip ID, package ID, and revision within a bhnd_device_match
* instance */
#define BHND_CHIP_IPR(_cid, _pkg, _rev) \
BHND_CHIP_ID(_cid), BHND_CHIP_PKG(_pkg), BHND_CHIP_REV(_rev)
#define BHND_MATCH_CHIP_IPR(_cid, _pkg, _rev) \
BHND_MATCH_CHIP_ID(_cid), \
BHND_MATCH_CHIP_PKG(_pkg), \
BHND_MATCH_CHIP_REV(_rev)
/** Set the required chip ID and revision within a bhnd_device_match
* instance */
#define BHND_CHIP_IR(_cid, _rev) \
BHND_CHIP_ID(_cid), BHND_CHIP_REV(_rev)
#define BHND_MATCH_CHIP_IR(_cid, _rev) \
BHND_MATCH_CHIP_ID(_cid), BHND_MATCH_CHIP_REV(_rev)
/**
* A bhnd(4) board match descriptor.
@ -252,9 +261,9 @@ struct bhnd_board_match {
struct bhnd_device_match {
/** Select fields to be matched */
union {
uint16_t match_flags;
uint32_t match_flags;
struct {
uint16_t
uint32_t
core_vendor:1,
core_id:1,
core_rev:1,
@ -264,11 +273,12 @@ struct bhnd_device_match {
chip_id:1,
chip_rev:1,
chip_pkg:1,
chip_type:1,
board_vendor:1,
board_type:1,
board_rev:1,
board_srom_rev:1,
flags_unused:1;
flags_unused:16;
} match;
} m;
@ -282,6 +292,7 @@ struct bhnd_device_match {
uint16_t chip_id; /**< required chip id */
struct bhnd_hwrev_match chip_rev; /**< matching chip revisions */
uint8_t chip_pkg; /**< required package */
uint8_t chip_type; /**< required chip type (BHND_CHIPTYPE_*) */
uint16_t board_vendor; /**< required board vendor */
uint16_t board_type; /**< required board type */

View File

@ -738,6 +738,9 @@ bhnd_chip_matches(const struct bhnd_chipid *chip,
!bhnd_hwrev_matches(chip->chip_rev, &desc->chip_rev))
return (false);
if (desc->m.match.chip_type && chip->chip_type != desc->chip_type)
return (false);
return (true);
}
@ -2317,3 +2320,14 @@ bhnd_bus_generic_deactivate_resource(device_t dev, device_t child,
return (EINVAL);
}
/**
* Helper function for implementing BHND_BUS_GET_INTR_DOMAIN().
*
* This implementation simply returns the address of nearest bhnd(4) bus,
* which may be @p dev; this behavior may be incompatible with FDT/OFW targets.
*/
uintptr_t
bhnd_bus_generic_get_intr_domain(device_t dev, device_t child, bool self)
{
return ((uintptr_t)dev);
}

View File

@ -98,10 +98,17 @@ bhnd_bhndb_find_hostb_device(device_t dev)
}
static int
bhnd_bhndb_assign_intr(device_t dev, device_t child, int rid)
bhnd_bhndb_map_intr(device_t dev, device_t child, u_int intr, rman_res_t *irq)
{
/* Delegate to parent bridge */
return (BHND_BUS_ASSIGN_INTR(device_get_parent(dev), child, rid));
return (BHND_BUS_MAP_INTR(device_get_parent(dev), child, intr, irq));
}
static void
bhnd_bhndb_unmap_intr(device_t dev, device_t child, rman_res_t irq)
{
/* Delegate to parent bridge */
return (BHND_BUS_UNMAP_INTR(device_get_parent(dev), child, irq));
}
static bhnd_clksrc
@ -131,13 +138,48 @@ bhnd_bhndb_pwrctl_ungate_clock(device_t dev, device_t child,
clock));
}
static int
bhnd_bhndb_setup_intr(device_t dev, device_t child, struct resource *irq,
int flags, driver_filter_t *filter, driver_intr_t *intr, void *arg,
void **cookiep)
{
device_t core, bus;
int error;
/* Find the actual bus-attached child core */
core = child;
while ((bus = device_get_parent(core)) != NULL) {
if (bus == dev)
break;
core = bus;
}
KASSERT(core != NULL, ("%s is not a child of %s",
device_get_nameunit(child), device_get_nameunit(dev)));
/* Ask our bridge to enable interrupt routing for the child core */
error = BHNDB_ROUTE_INTERRUPTS(device_get_parent(dev), core);
if (error)
return (error);
/* Delegate actual interrupt setup to the default bhnd bus
* implementation */
return (bhnd_generic_setup_intr(dev, child, irq, flags, filter, intr,
arg, cookiep));
}
static device_method_t bhnd_bhndb_methods[] = {
/* Bus interface */
DEVMETHOD(bus_setup_intr, bhnd_bhndb_setup_intr),
/* BHND interface */
DEVMETHOD(bhnd_bus_get_attach_type, bhnd_bhndb_get_attach_type),
DEVMETHOD(bhnd_bus_is_hw_disabled, bhnd_bhndb_is_hw_disabled),
DEVMETHOD(bhnd_bus_find_hostb_device, bhnd_bhndb_find_hostb_device),
DEVMETHOD(bhnd_bus_read_board_info, bhnd_bhndb_read_board_info),
DEVMETHOD(bhnd_bus_assign_intr, bhnd_bhndb_assign_intr),
DEVMETHOD(bhnd_bus_map_intr, bhnd_bhndb_map_intr),
DEVMETHOD(bhnd_bus_unmap_intr, bhnd_bhndb_unmap_intr),
DEVMETHOD(bhnd_bus_pwrctl_get_clksrc, bhnd_bhndb_pwrctl_get_clksrc),
DEVMETHOD(bhnd_bus_pwrctl_gate_clock, bhnd_bhndb_pwrctl_gate_clock),

View File

@ -804,7 +804,7 @@ bhndb_get_rman(struct bhndb_softc *sc, device_t child, int type)
case SYS_RES_MEMORY:
return (&sc->bus_res->br_mem_rman);
case SYS_RES_IRQ:
return (NULL);
return (&sc->bus_res->br_irq_rman);
default:
return (NULL);
}
@ -1088,9 +1088,9 @@ bhndb_adjust_resource(device_t dev, device_t child, int type,
if (!(rman_get_flags(r) & RF_ACTIVE))
goto done;
/* Otherwise, the range is limited to the existing register window
* mapping */
error = bhndb_find_resource_limits(sc->bus_res, r, &mstart, &mend);
/* Otherwise, the range is limited by the bridged resource mapping */
error = bhndb_find_resource_limits(sc->bus_res, type, r, &mstart,
&mend);
if (error)
goto done;
@ -1285,13 +1285,26 @@ bhndb_try_activate_resource(struct bhndb_softc *sc, device_t child, int type,
BHNDB_LOCK_ASSERT(sc, MA_NOTOWNED);
/* Only MMIO resources can be mapped via register windows */
if (type != SYS_RES_MEMORY)
return (ENXIO);
if (indirect)
if (indirect != NULL)
*indirect = false;
switch (type) {
case SYS_RES_IRQ:
/* IRQ resources are always directly mapped */
return (rman_activate_resource(r));
case SYS_RES_MEMORY:
/* Handled below */
break;
default:
device_printf(sc->dev, "unsupported resource type %d\n", type);
return (ENXIO);
}
/* Only MMIO resources can be mapped via register windows */
KASSERT(type == SYS_RES_MEMORY, ("invalid type: %d", type));
r_start = rman_get_start(r);
r_size = rman_get_size(r);
@ -1386,9 +1399,6 @@ bhndb_try_activate_resource(struct bhndb_softc *sc, device_t child, int type,
/**
* Default bhndb(4) implementation of BUS_ACTIVATE_RESOURCE().
*
* Maps resource activation requests to a viable static or dynamic
* register window, if any.
*/
static int
bhndb_activate_resource(device_t dev, device_t child, int type, int rid,
@ -1432,6 +1442,12 @@ bhndb_deactivate_resource(device_t dev, device_t child, int type,
if ((error = rman_deactivate_resource(r)))
return (error);
switch (type) {
case SYS_RES_IRQ:
/* No bridge-level state to be freed */
return (0);
case SYS_RES_MEMORY:
/* Free any dynamic window allocation. */
if (bhndb_get_addrspace(sc, child) == BHNDB_ADDRSPACE_BRIDGED) {
BHNDB_LOCK(sc);
@ -1442,6 +1458,11 @@ bhndb_deactivate_resource(device_t dev, device_t child, int type,
}
return (0);
default:
device_printf(dev, "unsupported resource type %d\n", type);
return (ENXIO);
}
}
/**
@ -1457,12 +1478,15 @@ bhndb_get_resource_list(device_t dev, device_t child)
/**
* Default bhndb(4) implementation of BHND_BUS_ACTIVATE_RESOURCE().
*
* For BHNDB_ADDRSPACE_NATIVE children, all resources may be assumed to
* be activated by the bridge.
* For BHNDB_ADDRSPACE_NATIVE children, all resources are activated as direct
* resources via BUS_ACTIVATE_RESOURCE().
*
* For BHNDB_ADDRSPACE_BRIDGED children, attempts to activate a static register
* window, a dynamic register window, or configures @p r as an indirect
* resource -- in that order.
* For BHNDB_ADDRSPACE_BRIDGED children, the resource priority is determined,
* and if possible, the resource is activated as a direct resource. For example,
* depending on resource priority and bridge resource availability, this
* function will attempt to activate SYS_RES_MEMORY resources using either a
* static register window, a dynamic register window, or it will configure @p r
* as an indirect resource -- in that order.
*/
static int
bhndb_activate_bhnd_resource(device_t dev, device_t child,
@ -1470,6 +1494,7 @@ bhndb_activate_bhnd_resource(device_t dev, device_t child,
{
struct bhndb_softc *sc;
struct bhndb_region *region;
bhndb_priority_t r_prio;
rman_res_t r_start, r_size;
int error;
bool indirect;
@ -1494,22 +1519,34 @@ bhndb_activate_bhnd_resource(device_t dev, device_t child,
r_start = rman_get_start(r->res);
r_size = rman_get_size(r->res);
/* Verify bridged address range's resource priority, and skip direct
/* Determine the resource priority of bridged resources, and skip direct
* allocation if the priority is too low. */
if (bhndb_get_addrspace(sc, child) == BHNDB_ADDRSPACE_BRIDGED) {
bhndb_priority_t r_prio;
switch (type) {
case SYS_RES_IRQ:
/* IRQ resources are always direct */
break;
region = bhndb_find_resource_region(sc->bus_res, r_start,
r_size);
case SYS_RES_MEMORY:
region = bhndb_find_resource_region(sc->bus_res,
r_start, r_size);
if (region != NULL)
r_prio = region->priority;
else
r_prio = BHNDB_PRIORITY_NONE;
/* If less than the minimum dynamic window priority, this
* resource should always be indirect. */
/* If less than the minimum dynamic window priority,
* this resource should always be indirect. */
if (r_prio < sc->bus_res->min_prio)
return (0);
break;
default:
device_printf(dev, "unsupported resource type %d\n",
type);
return (ENXIO);
}
}
/* Attempt direct activation */
@ -1791,6 +1828,217 @@ bhndb_bus_barrier(device_t dev, device_t child, struct bhnd_resource *r,
BHNDB_IO_COMMON_TEARDOWN();
}
/**
* Default bhndb(4) implementation of BHND_MAP_INTR().
*/
static int
bhndb_bhnd_map_intr(device_t dev, device_t child, u_int intr, rman_res_t *irq)
{
struct bhndb_softc *sc;
u_int ivec;
int error;
sc = device_get_softc(dev);
/* Is the intr valid? */
if (intr >= bhnd_get_intr_count(child))
return (EINVAL);
/* Fetch the interrupt vector */
if ((error = bhnd_get_intr_ivec(child, intr, &ivec)))
return (error);
/* Map directly to the actual backplane interrupt vector */
*irq = ivec;
return (0);
}
/**
* Default bhndb(4) implementation of BHND_UNMAP_INTR().
*/
static void
bhndb_bhnd_unmap_intr(device_t dev, device_t child, rman_res_t irq)
{
/* No state to clean up */
}
/**
* Default bhndb(4) implementation of BUS_SETUP_INTR().
*/
static int
bhndb_setup_intr(device_t dev, device_t child, struct resource *r,
int flags, driver_filter_t filter, driver_intr_t handler, void *arg,
void **cookiep)
{
struct bhndb_softc *sc;
struct bhndb_intr_isrc *isrc;
struct bhndb_intr_handler *ih;
int error;
sc = device_get_softc(dev);
/* Fetch the isrc */
if ((error = BHNDB_MAP_INTR_ISRC(dev, r, &isrc))) {
device_printf(dev, "failed to fetch isrc: %d\n", error);
return (error);
}
/* Allocate new ihandler entry */
ih = bhndb_alloc_intr_handler(child, r, isrc);
if (ih == NULL)
return (ENOMEM);
/* Perform actual interrupt setup via the host isrc */
error = bus_setup_intr(isrc->is_owner, isrc->is_res, flags, filter,
handler, arg, &ih->ih_cookiep);
if (error) {
bhndb_free_intr_handler(ih);
return (error);
}
/* Add to our interrupt handler list */
BHNDB_LOCK(sc);
bhndb_register_intr_handler(sc->bus_res, ih);
BHNDB_UNLOCK(sc);
/* Provide the interrupt handler entry as our cookiep value */
*cookiep = ih;
return (0);
}
/**
* Default bhndb(4) implementation of BUS_TEARDOWN_INTR().
*/
static int
bhndb_teardown_intr(device_t dev, device_t child, struct resource *r,
void *cookiep)
{
struct bhndb_softc *sc;
struct bhndb_intr_handler *ih;
struct bhndb_intr_isrc *isrc;
int error;
sc = device_get_softc(dev);
/* Locate and claim ownership of the interrupt handler entry */
BHNDB_LOCK(sc);
ih = bhndb_find_intr_handler(sc->bus_res, cookiep);
if (ih == NULL) {
panic("%s requested teardown of invalid cookiep %p",
device_get_nameunit(child), cookiep);
}
bhndb_deregister_intr_handler(sc->bus_res, ih);
BHNDB_UNLOCK(sc);
/* Perform actual interrupt teardown via the host isrc */
isrc = ih->ih_isrc;
error = bus_teardown_intr(isrc->is_owner, isrc->is_res, ih->ih_cookiep);
if (error) {
/* If teardown fails, we need to reinsert the handler entry
* to allow later teardown */
BHNDB_LOCK(sc);
bhndb_register_intr_handler(sc->bus_res, ih);
BHNDB_UNLOCK(sc);
return (error);
}
/* Free the entry */
bhndb_free_intr_handler(ih);
return (0);
}
/**
* Default bhndb(4) implementation of BUS_BIND_INTR().
*/
static int
bhndb_bind_intr(device_t dev, device_t child, struct resource *irq, int cpu)
{
struct bhndb_softc *sc;
struct bhndb_intr_handler *ih;
struct bhndb_intr_isrc *isrc;
sc = device_get_softc(dev);
isrc = NULL;
/* Fetch the isrc corresponding to the child IRQ resource */
BHNDB_LOCK(sc);
STAILQ_FOREACH(ih, &sc->bus_res->bus_intrs, ih_link) {
if (ih->ih_res == irq) {
isrc = ih->ih_isrc;
break;
}
}
BHNDB_UNLOCK(sc);
if (isrc == NULL) {
panic("%s requested bind of invalid irq %#jx-%#jx",
device_get_nameunit(child), rman_get_start(irq),
rman_get_end(irq));
}
/* Perform actual bind via the host isrc */
return (bus_bind_intr(isrc->is_owner, isrc->is_res, cpu));
}
/**
* Default bhndb(4) implementation of BUS_DESCRIBE_INTR().
*/
static int
bhndb_describe_intr(device_t dev, device_t child, struct resource *irq,
void *cookie, const char *descr)
{
struct bhndb_softc *sc;
struct bhndb_intr_handler *ih;
struct bhndb_intr_isrc *isrc;
sc = device_get_softc(dev);
/* Locate the interrupt handler entry; the caller owns the handler
* reference, and thus our entry is guaranteed to remain valid after
* we drop out lock below. */
BHNDB_LOCK(sc);
ih = bhndb_find_intr_handler(sc->bus_res, cookie);
if (ih == NULL) {
panic("%s requested invalid cookiep %p",
device_get_nameunit(child), cookie);
}
isrc = ih->ih_isrc;
BHNDB_UNLOCK(sc);
/* Perform the actual request via the host isrc */
return (BUS_DESCRIBE_INTR(device_get_parent(isrc->is_owner),
isrc->is_owner, isrc->is_res, ih->ih_cookiep, descr));
}
/**
* Default bhndb(4) implementation of BUS_CONFIG_INTR().
*/
static int
bhndb_config_intr(device_t dev, int irq, enum intr_trigger trig,
enum intr_polarity pol)
{
/* Unsupported */
return (ENXIO);
}
/**
* Default bhndb(4) implementation of BUS_REMAP_INTR().
*/
static int
bhndb_remap_intr(device_t dev, device_t child, u_int irq)
{
/* Unsupported */
return (ENXIO);
}
/**
* Default bhndb(4) implementation of BUS_GET_DMA_TAG().
*/
@ -1822,11 +2070,12 @@ static device_method_t bhndb_methods[] = {
DEVMETHOD(bus_activate_resource, bhndb_activate_resource),
DEVMETHOD(bus_deactivate_resource, bhndb_deactivate_resource),
DEVMETHOD(bus_setup_intr, bus_generic_setup_intr),
DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr),
DEVMETHOD(bus_config_intr, bus_generic_config_intr),
DEVMETHOD(bus_bind_intr, bus_generic_bind_intr),
DEVMETHOD(bus_describe_intr, bus_generic_describe_intr),
DEVMETHOD(bus_setup_intr, bhndb_setup_intr),
DEVMETHOD(bus_teardown_intr, bhndb_teardown_intr),
DEVMETHOD(bus_config_intr, bhndb_config_intr),
DEVMETHOD(bus_bind_intr, bhndb_bind_intr),
DEVMETHOD(bus_describe_intr, bhndb_describe_intr),
DEVMETHOD(bus_remap_intr, bhndb_remap_intr),
DEVMETHOD(bus_get_dma_tag, bhndb_get_dma_tag),
@ -1851,6 +2100,8 @@ static device_method_t bhndb_methods[] = {
DEVMETHOD(bhnd_bus_activate_resource, bhndb_activate_bhnd_resource),
DEVMETHOD(bhnd_bus_deactivate_resource, bhndb_deactivate_bhnd_resource),
DEVMETHOD(bhnd_bus_get_nvram_var, bhnd_bus_generic_get_nvram_var),
DEVMETHOD(bhnd_bus_map_intr, bhndb_bhnd_map_intr),
DEVMETHOD(bhnd_bus_unmap_intr, bhndb_bhnd_unmap_intr),
DEVMETHOD(bhnd_bus_get_service_registry,bhndb_get_service_registry),
DEVMETHOD(bhnd_bus_register_provider, bhnd_bus_generic_sr_register_provider),

View File

@ -1,7 +1,11 @@
#-
# Copyright (c) 2015 Landon Fuller <landon@landonf.org>
# Copyright (c) 2015-2016 Landon Fuller <landon@landonf.org>
# Copyright (c) 2017 The FreeBSD Foundation
# All rights reserved.
#
# Portions of this software were developed by Landon Fuller
# under sponsorship from the FreeBSD Foundation.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
@ -40,6 +44,7 @@
INTERFACE bhndb;
HEADER {
struct bhndb_intr_isrc;
struct bhndb_regwin;
struct bhndb_hw;
struct bhndb_hw_priority;
@ -90,12 +95,25 @@ CODE {
panic("bhndb_resume_resource unimplemented");
}
static int
bhndb_null_route_interrupts(device_t dev, device_t child)
{
panic("bhndb_route_interrupts unimplemented");
}
static int
bhndb_null_set_window_addr(device_t dev,
const struct bhndb_regwin *rw, bhnd_addr_t addr)
{
panic("bhndb_set_window_addr unimplemented");
}
static int
bhndb_null_map_intr_isrc(device_t dev, struct resource *irq,
struct bhndb_intr_isrc **isrc)
{
panic("bhndb_map_intr_isrc unimplemented");
}
}
/**
@ -207,6 +225,17 @@ METHOD int resume_resource {
struct resource *r;
} DEFAULT bhndb_null_resume_resource;
/**
* Enable bridge-level interrupt routing for @p child.
*
* @param dev The bridge device.
* @param child The bhnd child device for which interrupts should be routed.
*/
METHOD int route_interrupts {
device_t dev;
device_t child;
} DEFAULT bhndb_null_route_interrupts;
/**
* Set a given register window's base address.
*
@ -224,3 +253,22 @@ METHOD int set_window_addr {
const struct bhndb_regwin *win;
bhnd_addr_t addr;
} DEFAULT bhndb_null_set_window_addr;
/**
* Map a bridged interrupt resource to its corresponding host interrupt source,
* if any.
*
* @param dev The bridge device.
* @param irq The bridged interrupt resource.
* @param[out] isrc The host interrupt source to which the bridged interrupt
* is routed.
*
* @retval 0 success
* @retval non-zero if mapping @p irq otherwise fails, a regular unix error code
* will be returned.
*/
METHOD int map_intr_isrc {
device_t dev;
struct resource *irq;
struct bhndb_intr_isrc **isrc;
} DEFAULT bhndb_null_map_intr_isrc;

View File

@ -64,6 +64,8 @@ __FBSDID("$FreeBSD$");
#include <dev/bhnd/bhnd_erom.h>
#include <dev/bhnd/bhnd_eromvar.h>
#include <dev/bhnd/siba/sibareg.h>
#include <dev/bhnd/cores/pci/bhnd_pcireg.h>
#include "bhndb_pcireg.h"
@ -72,13 +74,15 @@ __FBSDID("$FreeBSD$");
struct bhndb_pci_eio;
static int bhndb_pci_init_msi(struct bhndb_pci_softc *sc);
static int bhndb_pci_alloc_msi(struct bhndb_pci_softc *sc,
int *msi_count);
static int bhndb_pci_read_core_table(device_t dev,
struct bhnd_chipid *chipid,
struct bhnd_core_info **cores, u_int *ncores,
bhnd_erom_class_t **eromcls);
static int bhndb_pci_add_children(struct bhndb_pci_softc *sc);
static bhnd_devclass_t bhndb_expected_pci_devclass(device_t dev);
static bool bhndb_is_pcie_attached(device_t dev);
static int bhndb_enable_pci_clocks(device_t dev);
@ -90,6 +94,11 @@ static int bhndb_pci_compat_setregwin(device_t dev,
static int bhndb_pci_fast_setregwin(device_t dev, device_t pci_dev,
const struct bhndb_regwin *, bhnd_addr_t);
static void bhndb_pci_write_core(struct bhndb_pci_softc *sc,
bus_size_t offset, uint32_t value, u_int width);
static uint32_t bhndb_pci_read_core(struct bhndb_pci_softc *sc,
bus_size_t offset, u_int width);
static void bhndb_init_sromless_pci_config(
struct bhndb_pci_softc *sc);
@ -106,7 +115,18 @@ static uint32_t bhndb_pci_eio_read(struct bhnd_erom_io *eio,
#define BHNDB_PCI_MSI_COUNT 1
/* bhndb_pci erom I/O implementation */
static struct bhndb_pci_quirk bhndb_pci_quirks[];
static struct bhndb_pci_quirk bhndb_pcie_quirks[];
static struct bhndb_pci_quirk bhndb_pcie2_quirks[];
static struct bhndb_pci_core bhndb_pci_cores[] = {
BHNDB_PCI_CORE(PCI, BHND_PCI_SRSH_PI_OFFSET, bhndb_pci_quirks),
BHNDB_PCI_CORE(PCIE, BHND_PCIE_SRSH_PI_OFFSET, bhndb_pcie_quirks),
BHNDB_PCI_CORE(PCIE2, BHND_PCIE_SRSH_PI_OFFSET, bhndb_pcie2_quirks),
BHNDB_PCI_CORE_END
};
/* bhndb_pci erom I/O instance state */
struct bhndb_pci_eio {
struct bhnd_erom_io eio;
device_t dev; /**< bridge device */
@ -120,6 +140,83 @@ struct bhndb_pci_eio {
bhnd_size_t size; /**< mapped size */
};
static struct bhndb_pci_quirk bhndb_pci_quirks[] = {
/* Backplane interrupt flags must be routed via siba-specific
* SIBA_CFG0_INTVEC configuration register; the BHNDB_PCI_INT_MASK
* PCI configuration register is unsupported. */
{{ BHND_MATCH_CHIP_TYPE (SIBA) },
{ BHND_MATCH_CORE_REV (HWREV_LTE(5)) },
BHNDB_PCI_QUIRK_SIBA_INTVEC },
/* All PCI core revisions require the SRSH work-around */
BHNDB_PCI_QUIRK(HWREV_ANY, BHNDB_PCI_QUIRK_SRSH_WAR),
BHNDB_PCI_QUIRK_END
};
static struct bhndb_pci_quirk bhndb_pcie_quirks[] = {
/* All PCIe-G1 core revisions require the SRSH work-around */
BHNDB_PCI_QUIRK(HWREV_ANY, BHNDB_PCI_QUIRK_SRSH_WAR),
BHNDB_PCI_QUIRK_END
};
static struct bhndb_pci_quirk bhndb_pcie2_quirks[] = {
/* All PCIe-G2 core revisions require the SRSH work-around */
BHNDB_PCI_QUIRK(HWREV_ANY, BHNDB_PCI_QUIRK_SRSH_WAR),
BHNDB_PCI_QUIRK_END
};
/**
* Return the device table entry for @p ci, or NULL if none.
*/
static struct bhndb_pci_core *
bhndb_pci_find_core(struct bhnd_core_info *ci)
{
for (size_t i = 0; !BHNDB_PCI_IS_CORE_END(&bhndb_pci_cores[i]); i++) {
struct bhndb_pci_core *entry = &bhndb_pci_cores[i];
if (bhnd_core_matches(ci, &entry->match))
return (entry);
}
return (NULL);
}
/**
* Return all quirk flags for the given @p cid and @p ci.
*/
static uint32_t
bhndb_pci_get_core_quirks(struct bhnd_chipid *cid, struct bhnd_core_info *ci)
{
struct bhndb_pci_core *entry;
struct bhndb_pci_quirk *qtable;
uint32_t quirks;
quirks = 0;
/* No core entry? */
if ((entry = bhndb_pci_find_core(ci)) == NULL)
return (quirks);
/* No quirks? */
if ((qtable = entry->quirks) == NULL)
return (quirks);
for (size_t i = 0; !BHNDB_PCI_IS_QUIRK_END(&qtable[i]); i++) {
struct bhndb_pci_quirk *q = &qtable[i];
if (!bhnd_chip_matches(cid, &q->chip_desc))
continue;
if (!bhnd_core_matches(ci, &q->core_desc))
continue;
quirks |= q->quirks;
}
return (quirks);
}
/**
* Default bhndb_pci implementation of device_probe().
*
@ -128,9 +225,16 @@ struct bhndb_pci_eio {
static int
bhndb_pci_probe(device_t dev)
{
struct bhnd_chipid cid;
struct bhnd_core_info *cores, hostb_core;
struct bhndb_pci_core *entry;
bhnd_devclass_t hostb_devclass;
u_int ncores;
device_t parent;
devclass_t parent_bus;
devclass_t pci;
devclass_t parent_bus, pci;
int error;
cores = NULL;
/* Our parent must be a PCI/PCIe device. */
pci = devclass_find("pci");
@ -140,35 +244,67 @@ bhndb_pci_probe(device_t dev)
if (parent_bus != pci)
return (ENXIO);
device_set_desc(dev, "PCI-BHND bridge");
/* Enable clocks */
if ((error = bhndb_enable_pci_clocks(dev)))
return (error);
return (BUS_PROBE_DEFAULT);
/* Identify the chip and enumerate the bridged cores */
error = bhndb_pci_read_core_table(dev, &cid, &cores, &ncores, NULL);
if (error)
goto cleanup;
/* Search our core table for the host bridge core */
hostb_devclass = bhndb_expected_pci_devclass(dev);
error = bhndb_find_hostb_core(cores, ncores, hostb_devclass,
&hostb_core);
if (error)
goto cleanup;
/* Look for a matching core table entry */
if ((entry = bhndb_pci_find_core(&hostb_core)) == NULL) {
error = ENXIO;
goto cleanup;
}
/* Configure MSI interrupts */
device_set_desc(dev, "PCI-BHND bridge");
/* fall-through */
error = BUS_PROBE_DEFAULT;
cleanup:
bhndb_disable_pci_clocks(dev);
if (cores != NULL)
free(cores, M_BHND);
return (error);
}
/**
* Attempt to allocate MSI interrupts, returning the count in @p msi_count
* on success.
*/
static int
bhndb_pci_init_msi(struct bhndb_pci_softc *sc)
bhndb_pci_alloc_msi(struct bhndb_pci_softc *sc, int *msi_count)
{
int error;
int error, count;
/* Is MSI available? */
if (pci_msi_count(sc->parent) < BHNDB_PCI_MSI_COUNT)
return (ENXIO);
/* Allocate expected message count */
sc->intr.msi_count = BHNDB_PCI_MSI_COUNT;
if ((error = pci_alloc_msi(sc->parent, &sc->intr.msi_count))) {
count = BHNDB_PCI_MSI_COUNT;
if ((error = pci_alloc_msi(sc->parent, &count))) {
device_printf(sc->dev, "failed to allocate MSI interrupts: "
"%d\n", error);
return (error);
}
if (sc->intr.msi_count < BHNDB_PCI_MSI_COUNT)
if (count < BHNDB_PCI_MSI_COUNT)
return (ENXIO);
/* MSI uses resource IDs starting at 1 */
sc->intr.intr_rid = 1;
*msi_count = count;
return (0);
}
@ -180,34 +316,46 @@ bhndb_pci_attach(device_t dev)
struct bhnd_core_info *cores, hostb_core;
bhnd_erom_class_t *erom_class;
u_int ncores;
int error, reg;
int irq_rid;
int error;
sc = device_get_softc(dev);
sc->dev = dev;
sc->parent = device_get_parent(dev);
sc->pci_devclass = bhndb_expected_pci_devclass(dev);
sc->pci_quirks = 0;
sc->set_regwin = NULL;
BHNDB_PCI_LOCK_INIT(sc);
cores = NULL;
/* Enable PCI bus mastering */
pci_enable_busmaster(sc->parent);
/* Set up PCI interrupt handling */
if (bhndb_pci_init_msi(sc) == 0) {
if (bhndb_pci_alloc_msi(sc, &sc->msi_count) == 0) {
/* MSI uses resource IDs starting at 1 */
irq_rid = 1;
device_printf(dev, "Using MSI interrupts on %s\n",
device_get_nameunit(sc->parent));
} else {
sc->msi_count = 0;
irq_rid = 0;
device_printf(dev, "Using INTx interrupts on %s\n",
device_get_nameunit(sc->parent));
sc->intr.intr_rid = 0;
}
/* Determine our bridge device class */
sc->pci_devclass = BHND_DEVCLASS_PCI;
if (pci_find_cap(sc->parent, PCIY_EXPRESS, &reg) == 0)
sc->pci_devclass = BHND_DEVCLASS_PCIE;
else
sc->pci_devclass = BHND_DEVCLASS_PCI;
sc->isrc = bhndb_alloc_intr_isrc(sc->parent, irq_rid, 0, RM_MAX_END, 1,
RF_SHAREABLE | RF_ACTIVE);
if (sc->isrc == NULL) {
device_printf(sc->dev, "failed to allocate interrupt "
"resource\n");
error = ENXIO;
goto cleanup;
}
/* Enable clocks (if required by this hardware) */
if ((error = bhndb_enable_pci_clocks(sc->dev)))
@ -226,12 +374,14 @@ bhndb_pci_attach(device_t dev)
sc->set_regwin = bhndb_pci_fast_setregwin;
}
/* Determine our host bridge core */
/* Determine our host bridge core and populate our quirk flags */
error = bhndb_find_hostb_core(cores, ncores, sc->pci_devclass,
&hostb_core);
if (error)
goto cleanup;
sc->pci_quirks = bhndb_pci_get_core_quirks(&cid, &hostb_core);
/* Perform bridge attach */
error = bhndb_attach(dev, &cid, cores, ncores, &hostb_core, erom_class);
if (error)
@ -256,7 +406,10 @@ bhndb_pci_attach(device_t dev)
device_delete_children(dev);
bhndb_disable_pci_clocks(sc->dev);
if (sc->intr.msi_count > 0)
if (sc->isrc != NULL)
bhndb_free_intr_isrc(sc->isrc);
if (sc->msi_count > 0)
pci_release_msi(dev);
if (cores != NULL)
@ -264,6 +417,8 @@ bhndb_pci_attach(device_t dev)
pci_disable_busmaster(sc->parent);
BHNDB_PCI_LOCK_DESTROY(sc);
return (error);
}
@ -287,13 +442,18 @@ bhndb_pci_detach(device_t dev)
if ((error = bhndb_disable_pci_clocks(sc->dev)))
return (error);
/* Free our interrupt resources */
bhndb_free_intr_isrc(sc->isrc);
/* Release MSI interrupts */
if (sc->intr.msi_count > 0)
if (sc->msi_count > 0)
pci_release_msi(dev);
/* Disable PCI bus mastering */
pci_disable_busmaster(sc->parent);
BHNDB_PCI_LOCK_DESTROY(sc);
return (0);
}
@ -541,6 +701,112 @@ bhndb_pci_sprom_size(struct bhndb_pci_softc *sc)
return (sprom_sz);
}
/**
* 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.
*
* @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)
{
const struct bhndb_regwin *win;
struct resource *r;
/* Locate the static register window mapping the PCI core */
win = bhndb_regwin_find_core(sc->bhndb.bus_res->cfg->register_windows,
sc->pci_devclass, 0, BHND_PORT_DEVICE, 0, 0);
if (win == NULL) {
device_printf(sc->dev, "missing PCI core register window\n");
return (ENXIO);
}
/* Fetch the resource containing the register window */
r = bhndb_host_resource_for_regwin(sc->bhndb.bus_res->res, win);
if (r == NULL) {
device_printf(sc->dev, "missing PCI core register resource\n");
return (ENXIO);
}
*res = r;
*offset = win->win_offset;
return (0);
}
/**
* Write a 1, 2, or 4 byte data item to the PCI core's registers at @p offset.
*
* @param sc bhndb PCI driver state.
* @param offset register write offset.
* @param value value to be written.
* @param width item width (1, 2, or 4 bytes).
*/
static void
bhndb_pci_write_core(struct bhndb_pci_softc *sc, bus_size_t offset,
uint32_t value, u_int width)
{
struct resource *r;
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);
switch (width) {
case 1:
bus_write_1(r, r_offset + offset, value);
break;
case 2:
bus_write_2(r, r_offset + offset, value);
break;
case 4:
bus_write_4(r, r_offset + offset, value);
break;
default:
panic("invalid width: %u", width);
}
}
/**
* Read a 1, 2, or 4 byte data item from the PCI core's registers
* at @p offset.
*
* @param sc bhndb PCI driver state.
* @param offset register read offset.
* @param width item width (1, 2, or 4 bytes).
*/
static uint32_t
bhndb_pci_read_core(struct bhndb_pci_softc *sc, bus_size_t offset, u_int width)
{
struct resource *r;
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);
switch (width) {
case 1:
return (bus_read_1(r, r_offset + offset));
case 2:
return (bus_read_2(r, r_offset + offset));
case 4:
return (bus_read_4(r, r_offset + offset));
default:
panic("invalid width: %u", width);
}
}
/*
* On devices without a SROM, the PCI(e) cores will be initialized with
* their Power-on-Reset defaults; this can leave two of the BAR0 PCI windows
@ -554,68 +820,31 @@ bhndb_pci_sprom_size(struct bhndb_pci_softc *sc)
static void
bhndb_init_sromless_pci_config(struct bhndb_pci_softc *sc)
{
struct bhndb_resources *bres;
const struct bhndb_hwcfg *cfg;
const struct bhndb_regwin *win;
struct bhnd_core_info hostb_core;
struct resource *core_regs;
bus_size_t srom_offset;
const struct bhndb_pci_core *pci_core;
bus_size_t srsh_offset;
u_int pci_cidx, sprom_cidx;
uint16_t val;
int error;
bres = sc->bhndb.bus_res;
cfg = bres->cfg;
/* Find our hostb core */
error = BHNDB_GET_HOSTB_CORE(sc->dev, sc->bhndb.bus_dev, &hostb_core);
if (error) {
device_printf(sc->dev, "no host bridge device found\n");
return;
}
if (hostb_core.vendor != BHND_MFGID_BCM)
if ((sc->pci_quirks & BHNDB_PCI_QUIRK_SRSH_WAR) == 0)
return;
switch (hostb_core.device) {
case BHND_COREID_PCI:
srom_offset = BHND_PCI_SRSH_PI_OFFSET;
break;
case BHND_COREID_PCIE:
srom_offset = BHND_PCIE_SRSH_PI_OFFSET;
break;
default:
device_printf(sc->dev, "unsupported PCI host bridge device\n");
return;
}
/* Determine the correct register offset for our PCI core */
pci_core = bhndb_pci_find_core(&sc->bhndb.bridge_core);
KASSERT(pci_core != NULL, ("missing core table entry"));
/* Locate the static register window mapping the PCI core */
win = bhndb_regwin_find_core(cfg->register_windows, sc->pci_devclass,
0, BHND_PORT_DEVICE, 0, 0);
if (win == NULL) {
device_printf(sc->dev, "missing PCI core register window\n");
return;
}
/* Fetch the resource containing the register window */
core_regs = bhndb_host_resource_for_regwin(bres->res, win);
if (core_regs == NULL) {
device_printf(sc->dev, "missing PCI core register resource\n");
return;
}
srsh_offset = pci_core->srsh_offset;
/* Fetch the SPROM's configured core index */
val = bus_read_2(core_regs, win->win_offset + srom_offset);
val = bhndb_pci_read_core(sc, srsh_offset, sizeof(val));
sprom_cidx = (val & BHND_PCI_SRSH_PI_MASK) >> BHND_PCI_SRSH_PI_SHIFT;
/* If it doesn't match host bridge's core index, update the index
* value */
pci_cidx = hostb_core.core_idx;
pci_cidx = sc->bhndb.bridge_core.core_idx;
if (sprom_cidx != pci_cidx) {
val &= ~BHND_PCI_SRSH_PI_MASK;
val |= (pci_cidx << BHND_PCI_SRSH_PI_SHIFT);
bus_write_2(core_regs,
win->win_offset + srom_offset, val);
bhndb_pci_write_core(sc, srsh_offset, val, sizeof(val));
}
}
@ -770,7 +999,22 @@ bhndb_pci_populate_board_info(device_t dev, device_t child,
}
/**
* Return true if the bridge device @p bhndb is attached via PCIe,
* Examine the bridge device @p dev and return the expected host bridge
* device class.
*
* @param dev The bhndb bridge device
*/
static bhnd_devclass_t
bhndb_expected_pci_devclass(device_t dev)
{
if (bhndb_is_pcie_attached(dev))
return (BHND_DEVCLASS_PCIE);
else
return (BHND_DEVCLASS_PCI);
}
/**
* Return true if the bridge device @p dev is attached via PCIe,
* false otherwise.
*
* @param dev The bhndb bridge device
@ -939,27 +1183,85 @@ bhndb_pci_pwrctl_ungate_clock(device_t dev, device_t child,
return (bhndb_enable_pci_clocks(sc->dev));
}
/**
* BHNDB_MAP_INTR_ISRC()
*/
static int
bhndb_pci_assign_intr(device_t dev, device_t child, int rid)
bhndb_pci_map_intr_isrc(device_t dev, struct resource *irq,
struct bhndb_intr_isrc **isrc)
{
struct bhndb_pci_softc *sc = device_get_softc(dev);
/* There's only one bridged interrupt to choose from */
*isrc = sc->isrc;
return (0);
}
/* siba-specific implementation of BHNDB_ROUTE_INTERRUPTS() */
static int
bhndb_pci_route_siba_interrupts(struct bhndb_pci_softc *sc, device_t child)
{
uint32_t sbintvec;
u_int ivec;
int error;
KASSERT(sc->pci_quirks & BHNDB_PCI_QUIRK_SIBA_INTVEC,
("route_siba_interrupts not supported by this hardware"));
/* Fetch the sbflag# for the child */
if ((error = bhnd_get_intr_ivec(child, 0, &ivec)))
return (error);
if (ivec > (sizeof(sbintvec)*8) - 1 /* aka '31' */) {
/* This should never be an issue in practice */
device_printf(sc->dev, "cannot route interrupts to high "
"sbflag# %u\n", ivec);
return (ENXIO);
}
BHNDB_PCI_LOCK(sc);
sbintvec = bhndb_pci_read_core(sc, SB0_REG_ABS(SIBA_CFG0_INTVEC), 4);
sbintvec |= (1 << ivec);
bhndb_pci_write_core(sc, SB0_REG_ABS(SIBA_CFG0_INTVEC), sbintvec, 4);
BHNDB_PCI_UNLOCK(sc);
return (0);
}
/* BHNDB_ROUTE_INTERRUPTS() */
static int
bhndb_pci_route_interrupts(device_t dev, device_t child)
{
struct bhndb_pci_softc *sc;
rman_res_t start, count;
int error;
struct bhnd_core_info core;
uint32_t core_bit;
uint32_t intmask;
sc = device_get_softc(dev);
/* Is the rid valid? */
if (rid >= bhnd_get_intr_count(child))
return (EINVAL);
if (sc->pci_quirks & BHNDB_PCI_QUIRK_SIBA_INTVEC)
return (bhndb_pci_route_siba_interrupts(sc, child));
/* Fetch our common PCI interrupt's start/count. */
error = bus_get_resource(sc->parent, SYS_RES_IRQ, sc->intr.intr_rid,
&start, &count);
if (error)
return (error);
core = bhnd_get_core_info(child);
if (core.core_idx > BHNDB_PCI_SBIM_COREIDX_MAX) {
/* This should never be an issue in practice */
device_printf(dev, "cannot route interrupts to high core "
"index %u\n", core.core_idx);
return (ENXIO);
}
/* Add to child's resource list */
return (bus_set_resource(child, SYS_RES_IRQ, rid, start, count));
BHNDB_PCI_LOCK(sc);
core_bit = (1<<core.core_idx) << BHNDB_PCI_SBIM_SHIFT;
intmask = pci_read_config(sc->parent, BHNDB_PCI_INT_MASK, 4);
intmask |= core_bit;
pci_write_config(sc->parent, BHNDB_PCI_INT_MASK, intmask, 4);
BHNDB_PCI_UNLOCK(sc);
return (0);
}
/**
@ -1156,8 +1458,6 @@ static device_method_t bhndb_pci_methods[] = {
DEVMETHOD(device_detach, bhndb_pci_detach),
/* BHND interface */
DEVMETHOD(bhnd_bus_assign_intr, bhndb_pci_assign_intr),
DEVMETHOD(bhnd_bus_pwrctl_get_clksrc, bhndb_pci_pwrctl_get_clksrc),
DEVMETHOD(bhnd_bus_pwrctl_gate_clock, bhndb_pci_pwrctl_gate_clock),
DEVMETHOD(bhnd_bus_pwrctl_ungate_clock, bhndb_pci_pwrctl_ungate_clock),
@ -1165,6 +1465,8 @@ static device_method_t bhndb_pci_methods[] = {
/* BHNDB interface */
DEVMETHOD(bhndb_set_window_addr, bhndb_pci_set_window_addr),
DEVMETHOD(bhndb_populate_board_info, bhndb_pci_populate_board_info),
DEVMETHOD(bhndb_map_intr_isrc, bhndb_pci_map_intr_isrc),
DEVMETHOD(bhndb_route_interrupts, bhndb_pci_route_interrupts),
DEVMETHOD_END
};

View File

@ -184,6 +184,7 @@
/* BHNDB_PCI_INT_MASK */
#define BHNDB_PCI_SBIM_SHIFT 8 /* backplane core interrupt mask bits offset */
#define BHNDB_PCI_SBIM_COREIDX_MAX 15 /**< maximum representible core index (in 16 bit field) */
#define BHNDB_PCI_SBIM_MASK 0xff00 /* backplane core interrupt mask */
#define BHNDB_PCI_SBIM_MASK_SERR 0x4 /* backplane SBErr interrupt mask */

View File

@ -1,7 +1,11 @@
/*-
* Copyright (c) 2015 Landon Fuller <landon@landonf.org>
* Copyright (c) 2015-2016 Landon Fuller <landon@landonf.org>
* Copyright (c) 2017 The FreeBSD Foundation
* All rights reserved.
*
* Portions of this software were developed by Landon Fuller
* under sponsorship from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
@ -48,20 +52,82 @@ struct bhndb_pci_softc;
typedef int (*bhndb_pci_set_regwin_t)(device_t dev, device_t pci_dev,
const struct bhndb_regwin *rw, bhnd_addr_t addr);
/* bhndb_pci interrupt state */
struct bhndb_pci_intr {
int msi_count; /**< MSI count, or 0 */
int intr_rid; /**< interrupt resource ID.*/
/**
* PCI/PCIe bridge-level device quirks
*/
enum {
/** No quirks */
BHNDB_PCI_QUIRK_NONE = 0,
/**
* The core requires fixup of the BAR0 SROM shadow to point at the
* current PCI core.
*/
BHNDB_PCI_QUIRK_SRSH_WAR = (1<<0),
/**
* The PCI (rev <= 5) core does not provide interrupt status/mask
* registers; these siba-only devices require routing backplane
* interrupt flags via the SIBA_CFG0_INTVEC register.
*/
BHNDB_PCI_QUIRK_SIBA_INTVEC = (1<<1),
};
/** bhndb_pci quirk table entry */
struct bhndb_pci_quirk {
struct bhnd_chip_match chip_desc; /**< chip match descriptor */
struct bhnd_core_match core_desc; /**< core match descriptor */
uint32_t quirks; /**< quirk flags */
};
#define BHNDB_PCI_QUIRK(_rev, _flags) { \
{ BHND_MATCH_ANY }, \
{ BHND_MATCH_CORE_REV(_rev) }, \
_flags, \
}
#define BHNDB_PCI_QUIRK_END \
{ { BHND_MATCH_ANY }, { BHND_MATCH_ANY }, 0 }
#define BHNDB_PCI_IS_QUIRK_END(_q) \
(BHND_MATCH_IS_ANY(&(_q)->core_desc) && \
BHND_MATCH_IS_ANY(&(_q)->chip_desc) && \
(_q)->quirks == 0)
/** bhndb_pci core table entry */
struct bhndb_pci_core {
struct bhnd_core_match match; /**< core match descriptor */
bus_size_t srsh_offset; /**< offset to SRSH_PI register, if any */
struct bhndb_pci_quirk *quirks; /**< quirk table */
};
#define BHNDB_PCI_CORE(_device, _srsh, _quirks) { \
{ BHND_MATCH_CORE(BHND_MFGID_BCM, BHND_COREID_ ## _device) }, \
_srsh, \
_quirks \
}
#define BHNDB_PCI_CORE_END { { BHND_MATCH_ANY }, 0, NULL }
#define BHNDB_PCI_IS_CORE_END(_c) BHND_MATCH_IS_ANY(&(_c)->match)
struct bhndb_pci_softc {
struct bhndb_softc bhndb; /**< parent softc */
device_t dev; /**< bridge device */
device_t parent; /**< parent PCI device */
bhnd_devclass_t pci_devclass; /**< PCI core's devclass */
struct bhndb_pci_intr intr; /**< PCI interrupt config */
uint32_t pci_quirks; /**< PCI bridge-level quirks */
int msi_count; /**< MSI count, or 0 */
struct bhndb_intr_isrc *isrc; /**< host interrupt source */
struct mtx mtx;
bhndb_pci_set_regwin_t set_regwin; /**< regwin handler */
};
#define BHNDB_PCI_LOCK_INIT(sc) \
mtx_init(&(sc)->mtx, device_get_nameunit((sc)->dev), \
"bhndb_pc state", MTX_DEF)
#define BHNDB_PCI_LOCK(sc) mtx_lock(&(sc)->mtx)
#define BHNDB_PCI_UNLOCK(sc) mtx_unlock(&(sc)->mtx)
#define BHNDB_PCI_LOCK_ASSERT(sc, what) mtx_assert(&(sc)->mtx, what)
#define BHNDB_PCI_LOCK_DESTROY(sc) mtx_destroy(&(sc)->mtx)
#endif /* _BHND_BHNDB_PCIVAR_H_ */

View File

@ -1,10 +1,14 @@
/*-
* Copyright (c) 2015-2016 Landon Fuller <landon@landonf.org>
* Copyright (c) 2017 The FreeBSD Foundation
* All rights reserved.
*
* Portions of this software were developed by Landon Fuller
* under sponsorship from the FreeBSD Foundation.
*
* Portions of this software were developed by Landon Fuller
* under sponsorship from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
@ -51,6 +55,7 @@
*/
struct bhndb_dw_alloc;
struct bhndb_intr_handler;
struct bhndb_region;
struct bhndb_resources;
@ -68,10 +73,26 @@ int bhndb_add_resource_region(
const struct bhndb_regwin *static_regwin);
int bhndb_find_resource_limits(
struct bhndb_resources *br,
struct bhndb_resources *br, int type,
struct resource *r, rman_res_t *start,
rman_res_t *end);
struct bhndb_intr_handler *bhndb_alloc_intr_handler(device_t owner,
struct resource *r,
struct bhndb_intr_isrc *isrc);
void bhndb_free_intr_handler(
struct bhndb_intr_handler *ih);
void bhndb_register_intr_handler(
struct bhndb_resources *br,
struct bhndb_intr_handler *ih);
void bhndb_deregister_intr_handler(
struct bhndb_resources *br,
struct bhndb_intr_handler *ih);
struct bhndb_intr_handler *bhndb_find_intr_handler(
struct bhndb_resources *br,
void *cookiep);
struct bhndb_region *bhndb_find_resource_region(
struct bhndb_resources *br,
bhnd_addr_t addr, bhnd_size_t size);
@ -136,6 +157,19 @@ struct bhndb_region {
STAILQ_ENTRY(bhndb_region) link;
};
/**
* Attached interrupt handler state
*/
struct bhndb_intr_handler {
device_t ih_owner; /**< child device */
struct resource *ih_res; /**< child resource */
void *ih_cookiep; /**< hostb-assigned cookiep, or NULL if bus_setup_intr() incomplete. */
struct bhndb_intr_isrc *ih_isrc; /**< host interrupt source routing the child's interrupt */
bool ih_active; /**< handler has been registered via bhndb_register_intr_handler */
STAILQ_ENTRY(bhndb_intr_handler) ih_link;
};
/**
* BHNDB resource allocation state.
*/
@ -147,6 +181,7 @@ struct bhndb_resources {
struct rman ht_mem_rman; /**< host memory manager */
struct rman br_mem_rman; /**< bridged memory manager */
struct rman br_irq_rman; /**< bridged irq manager */
STAILQ_HEAD(, bhndb_region) bus_regions; /**< bus region descriptors */
@ -155,6 +190,8 @@ struct bhndb_resources {
bitstr_t *dwa_freelist; /**< dynamic window free list */
bhndb_priority_t min_prio; /**< minimum resource priority required to
allocate a dynamic window */
STAILQ_HEAD(,bhndb_intr_handler) bus_intrs; /**< attached child interrupt handlers */
};
/**

View File

@ -270,10 +270,11 @@ bhndb_alloc_resources(device_t dev, device_t parent_dev,
bus_size_t last_window_size;
int rnid;
int error;
bool free_ht_mem, free_br_mem;
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)
@ -285,6 +286,7 @@ bhndb_alloc_resources(device_t dev, device_t parent_dev,
r->res = NULL;
r->min_prio = BHNDB_PRIORITY_NONE;
STAILQ_INIT(&r->bus_regions);
STAILQ_INIT(&r->bus_intrs);
/* Initialize host address space resource manager. */
r->ht_mem_rman.rm_start = 0;
@ -316,6 +318,25 @@ bhndb_alloc_resources(device_t dev, device_t parent_dev,
goto failed;
}
/* Initialize resource manager for the bridged interrupt controller. */
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;
}
/* Fetch the dynamic regwin count and verify that it does not exceed
* what is representable via our freelist bitstring. */
r->dwa_count = bhndb_regwin_count(cfg->register_windows,
@ -455,6 +476,9 @@ bhndb_alloc_resources(device_t dev, device_t parent_dev,
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);
@ -480,6 +504,11 @@ 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;
/* No window regions may still be held */
if (!bhndb_dw_all_free(br)) {
@ -492,9 +521,21 @@ bhndb_free_resources(struct bhndb_resources *br)
device_printf(br->dev,
"leaked dynamic register window %d\n", dwa->rnid);
leaked_regions = true;
}
}
/* There should be no interrupt handlers still registered */
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" : "");
}
/* Release host resources allocated through our parent. */
if (br->res != NULL)
bhndb_release_host_resources(br->res);
@ -518,6 +559,7 @@ bhndb_free_resources(struct bhndb_resources *br)
/* Release our resource managers */
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);
@ -666,6 +708,222 @@ bhndb_find_hostb_core(struct bhnd_core_info *cores, u_int ncores,
return (0);
}
/**
* Allocate a host interrupt source and its backing SYS_RES_IRQ host resource.
*
* @param owner The device to be used to allocate a SYS_RES_IRQ
* resource with @p rid.
* @param rid The resource ID of the IRQ to be allocated.
* @param start The start value to be passed to bus_alloc_resource().
* @param end The end value to be passed to bus_alloc_resource().
* @param count The count to be passed to bus_alloc_resource().
* @param flags The flags to be passed to bus_alloc_resource().
*
* @retval non-NULL success
* @retval NULL if allocation fails.
*/
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);
}
/**
* Free a host interrupt source and its backing host resource.
*
* @param isrc The interrupt source to be freed.
*/
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);
}
/**
* Allocate and initialize a new interrupt handler entry.
*
* @param owner The child device that owns this entry.
* @param r The child's interrupt resource.
* @param isrc The isrc mapped for this entry.
*
* @retval non-NULL success
* @retval NULL if allocation fails.
*/
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);
}
/**
* Free an interrupt handler entry.
*
* @param br The resource state owning @p ih.
* @param ih The interrupt handler entry to be removed.
*/
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);
}
/**
* Add an active interrupt handler to the given resource state.
*
* @param br The resource state to be modified.
* @param ih The interrupt handler entry to be added.
*/
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);
}
/**
* Remove an interrupt handler from the given resource state.
*
* @param br The resource state containing @p ih.
* @param ih The interrupt handler entry to be removed.
*/
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;
}
/**
* Return the interrupt handler entry corresponding to @p cookiep, or NULL
* if no entry is found.
*
* @param br The resource state to search for the given @p cookiep.
* @param cookiep The interrupt handler's bus-assigned cookiep value.
*/
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);
}
/* Not found */
return (NULL);
}
/**
* Find the maximum start and end limits of the bridged resource @p r.
*
* If the resource is not currently mapped by the bridge, ENOENT will be
* returned.
*
* @param br The resource state to search.
* @param type The resource type (see SYS_RES_*).
* @param r The resource to search for in @p br.
* @param[out] start On success, the minimum supported start address.
* @param[out] end On success, the maximum supported end address.
*
* @retval 0 success
* @retval ENOENT no active mapping found for @p r of @p type
*/
int
bhndb_find_resource_limits(struct bhndb_resources *br, int type,
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 (type) {
case SYS_RES_IRQ:
/* Is this one of ours? */
STAILQ_FOREACH(ih, &br->bus_intrs, ih_link) {
if (ih->ih_res == r)
continue;
/* We don't support adjusting IRQ resource limits */
*start = rman_get_start(r);
*end = rman_get_end(r);
return (0);
}
/* Not found */
return (ENOENT);
case SYS_RES_MEMORY: {
/* Check for an enclosing dynamic register window */
if ((dynamic = bhndb_dw_find_resource(br, r))) {
*start = dynamic->target;
*end = dynamic->target + dynamic->win->win_size - 1;
return (0);
}
/* Check for a static region */
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);
}
/* Not found */
return (ENOENT);
}
default:
device_printf(br->dev, "unknown resource type: %d\n", type);
return (ENOENT);
}
}
/**
* Add a bus region entry to @p r for the given base @p addr and @p size.
*
@ -705,49 +963,6 @@ bhndb_add_resource_region(struct bhndb_resources *br, bhnd_addr_t addr,
}
/**
* Find the maximum start and end limits of the register window mapping
* resource @p r.
*
* If the memory range is not mapped by an existing dynamic or static register
* window, ENOENT will be returned.
*
* @param br The resource state to search.
* @param r The resource to search for in @p br.
* @param addr The requested starting address.
* @param size The requested size.
*
* @retval bhndb_region A region that fully contains the requested range.
* @retval NULL If no mapping region can be found.
*/
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;
/* Check for an enclosing dynamic register window */
if ((dynamic = bhndb_dw_find_resource(br, r))) {
*start = dynamic->target;
*end = dynamic->target + dynamic->win->win_size - 1;
return (0);
}
/* Check for a static region */
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);
}
/* Not found */
return (ENOENT);
}
/**
* Find the bus region that maps @p size bytes at @p addr.
*

View File

@ -55,6 +55,8 @@
DECLARE_CLASS(bhndb_driver);
/* forward declarations */
struct bhndb_intr_isrc;
struct bhndb_resources;
struct bhndb_host_resources;
@ -82,6 +84,12 @@ int bhndb_find_hostb_core(
bhnd_devclass_t bridge_devclass,
struct bhnd_core_info *core);
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);
void bhndb_free_intr_isrc(
struct bhndb_intr_isrc *isrc);
int bhndb_alloc_host_resources(device_t dev,
const struct bhndb_hwcfg *hwcfg,
struct bhndb_host_resources **resources);
@ -136,6 +144,15 @@ struct bhndb_devinfo {
struct resource_list resources; /**< child resources. */
};
/**
* Host interrupt source to which bridged interrupts may be routed.
*/
struct bhndb_intr_isrc {
device_t is_owner; /**< host device (e.g. the pci device). */
struct resource *is_res; /**< irq resource */
int is_rid; /**< irq resource ID */
};
/**
* Host resources allocated for a bridge hardware configuration.
*/
@ -162,6 +179,7 @@ struct bhndb_softc {
struct mtx sc_mtx; /**< resource lock. */
struct bhndb_resources *bus_res; /**< bus resource state */
STAILQ_HEAD(,bhndb_intr_handler) bus_intrs; /**< attached child interrupt handlers */
};
#endif /* _BHND_BHNDBVAR_H_ */

View File

@ -86,6 +86,12 @@ int bhnd_generic_suspend_child(device_t dev,
int bhnd_generic_resume_child(device_t dev,
device_t child);
int bhnd_generic_setup_intr(device_t dev,
device_t child, struct resource *irq,
int flags, driver_filter_t *filter,
driver_intr_t *intr, void *arg,
void **cookiep);
int bhnd_generic_get_nvram_var(device_t dev,
device_t child, const char *name,
void *buf, size_t *size,

View File

@ -112,9 +112,6 @@ static struct bhnd_device_quirk chipc_quirks[] = {
BHND_DEVICE_QUIRK_END
};
// FIXME: IRQ shouldn't be hard-coded
#define CHIPC_MIPS_IRQ 2
static int chipc_add_children(struct chipc_softc *sc);
static bhnd_nvram_src chipc_find_nvram_src(struct chipc_softc *sc,
@ -274,11 +271,14 @@ chipc_add_children(struct chipc_softc *sc)
}
/* Both OTP and external SPROM are mapped at CHIPC_SPROM_OTP */
error = chipc_set_resource(sc, child, SYS_RES_MEMORY, 0,
CHIPC_SPROM_OTP, CHIPC_SPROM_OTP_SIZE, 0, 0);
if (error)
error = chipc_set_mem_resource(sc, child, 0, CHIPC_SPROM_OTP,
CHIPC_SPROM_OTP_SIZE, 0, 0);
if (error) {
device_printf(sc->dev, "failed to set OTP memory "
"resource: %d\n", error);
return (error);
}
}
/*
* PMU/PWR_CTRL
@ -300,6 +300,11 @@ chipc_add_children(struct chipc_softc *sc)
/* UARTs */
for (u_int i = 0; i < min(sc->caps.num_uarts, CHIPC_UART_MAX); i++) {
int irq_rid, mem_rid;
irq_rid = 0;
mem_rid = 0;
child = BUS_ADD_CHILD(sc->dev, 0, "uart", -1);
if (child == NULL) {
device_printf(sc->dev, "failed to add uart%u\n", i);
@ -307,24 +312,28 @@ chipc_add_children(struct chipc_softc *sc)
}
/* Shared IRQ */
error = bus_set_resource(child, SYS_RES_IRQ, 0, CHIPC_MIPS_IRQ,
1);
error = chipc_set_irq_resource(sc, child, irq_rid, 0);
if (error) {
device_printf(sc->dev, "failed to set uart%u irq %u\n",
i, CHIPC_MIPS_IRQ);
i, 0);
return (error);
}
/* UART registers are mapped sequentially */
error = chipc_set_resource(sc, child, SYS_RES_MEMORY, 0,
error = chipc_set_mem_resource(sc, child, mem_rid,
CHIPC_UART(i), CHIPC_UART_SIZE, 0, 0);
if (error)
if (error) {
device_printf(sc->dev, "failed to set uart%u memory "
"resource: %d\n", i, error);
return (error);
}
}
/* Flash */
flash_bus = chipc_flash_bus_name(sc->caps.flash_type);
if (flash_bus != NULL) {
int rid;
child = BUS_ADD_CHILD(sc->dev, 0, flash_bus, -1);
if (child == NULL) {
device_printf(sc->dev, "failed to add %s device\n",
@ -333,17 +342,25 @@ chipc_add_children(struct chipc_softc *sc)
}
/* flash memory mapping */
error = chipc_set_resource(sc, child, SYS_RES_MEMORY, 0,
0, RM_MAX_END, 1, 1);
if (error)
rid = 0;
error = chipc_set_mem_resource(sc, child, rid, 0, RM_MAX_END, 1,
1);
if (error) {
device_printf(sc->dev, "failed to set flash memory "
"resource %d: %d\n", rid, error);
return (error);
}
/* flashctrl registers */
error = chipc_set_resource(sc, child, SYS_RES_MEMORY, 1,
rid++;
error = chipc_set_mem_resource(sc, child, rid,
CHIPC_SFLASH_BASE, CHIPC_SFLASH_SIZE, 0, 0);
if (error)
if (error) {
device_printf(sc->dev, "failed to set flash memory "
"resource %d: %d\n", rid, error);
return (error);
}
}
return (0);
}
@ -592,6 +609,7 @@ chipc_add_child(device_t dev, u_int order, const char *name, int unit)
}
resource_list_init(&dinfo->resources);
dinfo->irq_mapped = false;
device_set_ivars(child, dinfo);
return (child);
@ -603,7 +621,15 @@ chipc_child_deleted(device_t dev, device_t child)
struct chipc_devinfo *dinfo = device_get_ivars(child);
if (dinfo != NULL) {
/* Free the child's resource list */
resource_list_free(&dinfo->resources);
/* Unmap the child's IRQ */
if (dinfo->irq_mapped) {
bhnd_unmap_intr(dev, dinfo->irq);
dinfo->irq_mapped = false;
}
free(dinfo, M_BHND);
}
@ -731,8 +757,7 @@ chipc_get_rman(struct chipc_softc *sc, int type)
return (&sc->mem_rman);
case SYS_RES_IRQ:
/* IRQs can be used with RF_SHAREABLE, so we don't perform
* any local proxying of resource requests. */
/* We delegate IRQ resource management to the parent bus */
return (NULL);
default:

View File

@ -1,7 +1,11 @@
/*-
* Copyright (c) 2015-2016 Landon Fuller <landon@landonf.org>
* Copyright (c) 2017 The FreeBSD Foundation
* All rights reserved.
*
* Portions of this software were developed by Landon Fuller
* under sponsorship from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
@ -55,10 +59,11 @@ int chipc_init_child_resource(struct resource *r,
struct resource *parent,
bhnd_size_t offset, bhnd_size_t size);
int chipc_set_resource(struct chipc_softc *sc,
device_t child, int type, int rid,
rman_res_t start, rman_res_t count, u_int port,
u_int region);
int chipc_set_irq_resource(struct chipc_softc *sc,
device_t child, int rid, u_int intr);
int chipc_set_mem_resource(struct chipc_softc *sc,
device_t child, int rid, rman_res_t start,
rman_res_t count, u_int port, u_int region);
struct chipc_region *chipc_alloc_region(struct chipc_softc *sc,
bhnd_port_type type, u_int port,

View File

@ -162,22 +162,69 @@ chipc_init_child_resource(struct resource *r,
}
/**
* Associate a resource with a given resource ID, relative to the given
* port and region.
* Map an interrupt line to an IRQ, and then register a corresponding SYS_RES_IRQ
* with @p child's resource list.
*
* This function behaves identically to bus_set_resource() for all resource
* types other than SYS_RES_MEMORY.
* @param sc chipc driver state.
* @param child The device to set the resource on.
* @param rid The resource ID.
* @param intr The interrupt line to be mapped.
* @param count The length of the resource.
* @param port The mapping port number (ignored if not SYS_RES_MEMORY).
* @param region The mapping region number (ignored if not SYS_RES_MEMORY).
*/
int
chipc_set_irq_resource(struct chipc_softc *sc, device_t child, int rid,
u_int intr)
{
struct chipc_devinfo *dinfo;
int error;
KASSERT(device_get_parent(child) == sc->dev, ("not a direct child"));
dinfo = device_get_ivars(child);
/* We currently only support a single IRQ mapping */
if (dinfo->irq_mapped) {
device_printf(sc->dev, "irq already mapped for child\n");
return (ENOMEM);
}
/* Map the IRQ */
if ((error = bhnd_map_intr(sc->dev, intr, &dinfo->irq))) {
device_printf(sc->dev, "failed to map intr %u: %d\n", intr,
error);
return (error);
}
dinfo->irq_mapped = true;
/* Add to child's resource list */
error = bus_set_resource(child, SYS_RES_IRQ, rid, dinfo->irq, 1);
if (error) {
device_printf(sc->dev, "failed to set child irq resource %d to "
"%ju: %d\n", rid, dinfo->irq, error);
bhnd_unmap_intr(sc->dev, dinfo->irq);
return (error);
}
return (0);
}
/**
* Add a SYS_RES_MEMORY resource with a given resource ID, relative to the
* given port and region, to @p child's resource list.
*
* For SYS_RES_MEMORY resources, the specified @p region's address and size
* will be fetched from the bhnd(4) bus, and bus_set_resource() will be called
* with @p start added the region's actual base address.
* The specified @p region's address and size will be fetched from the bhnd(4)
* bus, and bus_set_resource() will be called with @p start added the region's
* actual base address.
*
* To use the default region values for @p start and @p count, specify
* a @p start value of 0ul, and an end value of RMAN_MAX_END
*
* @param sc chipc driver state.
* @param child The device to set the resource on.
* @param type The resource type.
* @param rid The resource ID.
* @param start The resource start address (if SYS_RES_MEMORY, this is
* relative to @p region's base address).
@ -186,7 +233,7 @@ chipc_init_child_resource(struct resource *r,
* @param region The mapping region number (ignored if not SYS_RES_MEMORY).
*/
int
chipc_set_resource(struct chipc_softc *sc, device_t child, int type, int rid,
chipc_set_mem_resource(struct chipc_softc *sc, device_t child, int rid,
rman_res_t start, rman_res_t count, u_int port, u_int region)
{
bhnd_addr_t region_addr;
@ -194,9 +241,7 @@ chipc_set_resource(struct chipc_softc *sc, device_t child, int type, int rid,
bool isdefault;
int error;
if (type != SYS_RES_MEMORY)
return (bus_set_resource(child, type, rid, start, count));
KASSERT(device_get_parent(child) == sc->dev, ("not a direct child"));
isdefault = RMAN_IS_DEFAULT_RANGE(start, count);
/* Fetch region address and size */
@ -224,7 +269,8 @@ chipc_set_resource(struct chipc_softc *sc, device_t child, int type, int rid,
return (ERANGE);
}
return (bus_set_resource(child, type, rid, region_addr + start, count));
return (bus_set_resource(child, SYS_RES_MEMORY, rid,
region_addr + start, count));
}

View File

@ -1,7 +1,11 @@
/*-
* Copyright (c) 2015 Landon Fuller <landon@landonf.org>
* Copyright (c) 2015-2016 Landon Fuller <landon@landonf.org>
* Copyright (c) 2017 The FreeBSD Foundation
* All rights reserved.
*
* Portions of this software were developed by Landon Fuller
* under sponsorship from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
@ -138,6 +142,8 @@ enum {
*/
struct chipc_devinfo {
struct resource_list resources; /**< child resources */
rman_res_t irq; /**< child IRQ, if mapped */
bool irq_mapped; /**< true if IRQ mapped, false otherwise */
};
/**

View File

@ -139,13 +139,13 @@ static const struct bhnd_device_quirk bhnd_pcie_quirks[] = {
BHND_PCIE_QUIRK_BFL2_PCIEWAR_EN },
/* Apple BCM4322 boards that require 700mV SerDes TX drive strength. */
{{ BHND_CHIP_ID(BCM4322),
{{ BHND_MATCH_CHIP_ID(BCM4322),
BHND_MATCH_BOARD(PCI_VENDOR_APPLE, BCM94322X9), },
BHND_PCIE_QUIRK_SERDES_TXDRV_700MV },
/* Apple BCM4331 board-specific quirks */
#define BHND_A4331_QUIRK(_board, ...) \
{{ BHND_CHIP_ID(BCM4331), \
{{ BHND_MATCH_CHIP_ID(BCM4331), \
BHND_MATCH_BOARD(PCI_VENDOR_APPLE, _board) }, __VA_ARGS__ }
BHND_A4331_QUIRK(BCM94331X19, BHND_PCIE_QUIRK_SERDES_TXDRV_MAX |

View File

@ -137,18 +137,6 @@ bhnd_usb_attach(device_t dev)
panic("%s: sc->mem_rman", __func__);
}
sc->irq_rman.rm_start = sc->sc_irqn;
sc->irq_rman.rm_end = sc->sc_irqn;
sc->irq_rman.rm_type = RMAN_ARRAY;
sc->irq_rman.rm_descr = "BHND USB core IRQ";
/*
* BHND USB share same IRQ between OHCI and EHCI
*/
if (rman_init(&sc->irq_rman) != 0 ||
rman_manage_region(&sc->irq_rman, sc->irq_rman.rm_start,
sc->irq_rman.rm_end) != 0)
panic("%s: failed to set up IRQ rman", __func__);
/* TODO: macros for registers */
bus_write_4(sc->sc_mem, 0x200, 0x7ff);
DELAY(100);
@ -254,17 +242,19 @@ bhnd_usb_alloc_resource(device_t bus, device_t child, int type, int *rid,
struct resource *rv;
struct resource_list *rl;
struct resource_list_entry *rle;
int isdefault, needactivate;
int passthrough, isdefault, needactivate;
struct bhnd_usb_softc *sc = device_get_softc(bus);
isdefault = RMAN_IS_DEFAULT_RANGE(start,end);
passthrough = (device_get_parent(child) != bus);
needactivate = flags & RF_ACTIVE;
rl = BUS_GET_RESOURCE_LIST(bus, child);
rle = NULL;
if (isdefault) {
if (!passthrough && isdefault) {
BHND_INFO_DEV(bus, "trying allocate def %d - %d for %s", type,
*rid, device_get_nameunit(child) );
rl = BUS_GET_RESOURCE_LIST(bus, child);
rle = resource_list_find(rl, type, *rid);
if (rle == NULL)
return (NULL);
@ -303,32 +293,11 @@ bhnd_usb_alloc_resource(device_t bus, device_t child, int type, int *rid,
return (rv);
}
if (type == SYS_RES_IRQ) {
rv = rman_reserve_resource(&sc->irq_rman, start, end, count,
flags, child);
if (rv == NULL) {
BHND_ERROR_DEV(bus, "could not reserve resource");
return (0);
}
rman_set_rid(rv, *rid);
if (needactivate &&
bus_activate_resource(child, type, *rid, rv)) {
BHND_ERROR_DEV(bus, "could not activate resource");
rman_release_resource(rv);
return (0);
}
return (rv);
}
/*
* Pass the request to the parent.
*/
return (resource_list_alloc(rl, bus, child, type, rid,
start, end, count, flags));
return (bus_generic_rl_alloc_resource(bus, child, type, rid, start, end,
count, flags));
}
static struct resource_list *
@ -345,17 +314,38 @@ static int
bhnd_usb_release_resource(device_t dev, device_t child, int type,
int rid, struct resource *r)
{
struct resource_list *rl;
struct bhnd_usb_softc *sc;
struct resource_list_entry *rle;
bool passthrough;
int error;
rl = bhnd_usb_get_reslist(dev, child);
if (rl == NULL)
return (EINVAL);
rle = resource_list_find(rl, type, rid);
if (rle == NULL)
return (EINVAL);
rman_release_resource(r);
sc = device_get_softc(dev);
passthrough = (device_get_parent(child) != dev);
/* Delegate to our parent device's bus if the requested resource type
* isn't handled locally. */
if (type != SYS_RES_MEMORY) {
return (bus_generic_rl_release_resource(dev, child, type, rid,
r));
}
/* Deactivate resources */
if (rman_get_flags(r) & RF_ACTIVE) {
error = BUS_DEACTIVATE_RESOURCE(dev, child, type, rid, r);
if (error)
return (error);
}
if ((error = rman_release_resource(r)))
return (error);
if (!passthrough) {
/* Clean resource list entry */
rle = resource_list_find(BUS_GET_RESOURCE_LIST(dev, child),
type, rid);
if (rle != NULL)
rle->res = NULL;
}
return (0);
}
@ -400,56 +390,84 @@ bhnd_usb_add_child(device_t dev, u_int order, const char *name, int unit)
struct bhnd_usb_softc *sc;
struct bhnd_usb_devinfo *sdi;
device_t child;
int error;
sc = device_get_softc(dev);
child = device_add_child_ordered(dev, order, name, unit);
if (child == NULL)
return (NULL);
sdi = malloc(sizeof(struct bhnd_usb_devinfo), M_DEVBUF, M_NOWAIT|M_ZERO);
if (sdi == NULL)
return (NULL);
resource_list_init(&sdi->sdi_rl);
sdi->sdi_irq_mapped = false;
if (strncmp(name, "ohci", 4) == 0)
{
sdi->sdi_maddr = sc->sc_maddr + 0x000;
sdi->sdi_msize = 0x200;
sdi->sdi_irq = sc->sc_irqn;
BHND_INFO_DEV(dev, "ohci: irq=%d maddr=0x%jx", sdi->sdi_irq,
sdi->sdi_maddr);
}
else if (strncmp(name, "ehci", 4) == 0)
{
sdi->sdi_maddr = sc->sc_maddr + 0x000;
sdi->sdi_msize = 0x1000;
sdi->sdi_irq = sc->sc_irqn;
BHND_INFO_DEV(dev, "ehci: irq=%d maddr=0x%jx", sdi->sdi_irq,
sdi->sdi_maddr);
}
else
{
panic("Unknown subdevice");
/* Unknown subdevice */
sdi->sdi_maddr = 1;
sdi->sdi_msize = 1;
sdi->sdi_irq = 1;
}
resource_list_init(&sdi->sdi_rl);
/* Map the child's IRQ */
if ((error = bhnd_map_intr(dev, 0, &sdi->sdi_irq))) {
BHND_ERROR_DEV(dev, "could not map %s interrupt: %d", name,
error);
goto failed;
}
sdi->sdi_irq_mapped = true;
BHND_INFO_DEV(dev, "%s: irq=%ju maddr=0x%jx", name, sdi->sdi_irq,
sdi->sdi_maddr);
/*
* Determine memory window on bus and irq if one is needed.
* Add memory window and irq to child's resource list.
*/
resource_list_add(&sdi->sdi_rl, SYS_RES_MEMORY, 0,
sdi->sdi_maddr, sdi->sdi_maddr + sdi->sdi_msize - 1, sdi->sdi_msize);
resource_list_add(&sdi->sdi_rl, SYS_RES_MEMORY, 0, sdi->sdi_maddr,
sdi->sdi_maddr + sdi->sdi_msize - 1, sdi->sdi_msize);
resource_list_add(&sdi->sdi_rl, SYS_RES_IRQ, 0,
sdi->sdi_irq, sdi->sdi_irq, 1);
resource_list_add(&sdi->sdi_rl, SYS_RES_IRQ, 0, sdi->sdi_irq,
sdi->sdi_irq, 1);
child = device_add_child_ordered(dev, order, name, unit);
if (child == NULL) {
BHND_ERROR_DEV(dev, "could not add %s", name);
goto failed;
}
device_set_ivars(child, sdi);
return (child);
failed:
if (sdi->sdi_irq_mapped)
bhnd_unmap_intr(dev, sdi->sdi_irq);
resource_list_free(&sdi->sdi_rl);
free(sdi, M_DEVBUF);
return (NULL);
}
static void
bhnd_usb_child_deleted(device_t dev, device_t child)
{
struct bhnd_usb_devinfo *dinfo;
if ((dinfo = device_get_ivars(child)) == NULL)
return;
if (dinfo->sdi_irq_mapped)
bhnd_unmap_intr(dev, dinfo->sdi_irq);
resource_list_free(&dinfo->sdi_rl);
free(dinfo, M_DEVBUF);
}
static device_method_t bhnd_usb_methods[] = {
@ -459,6 +477,7 @@ static device_method_t bhnd_usb_methods[] = {
/* Bus interface */
DEVMETHOD(bus_add_child, bhnd_usb_add_child),
DEVMETHOD(bus_child_deleted, bhnd_usb_child_deleted),
DEVMETHOD(bus_alloc_resource, bhnd_usb_alloc_resource),
DEVMETHOD(bus_get_resource_list, bhnd_usb_get_reslist),
DEVMETHOD(bus_print_child, bhnd_usb_print_child),

View File

@ -50,7 +50,8 @@ struct bhnd_usb_softc {
struct bhnd_usb_devinfo {
struct resource_list sdi_rl;
uint8_t sdi_unit; /* core index on bus */
uint8_t sdi_irq;
rman_res_t sdi_irq; /**< child IRQ, if mapped */
bool sdi_irq_mapped; /**< true if IRQ mapped, false otherwise */
char sdi_name[8];
rman_res_t sdi_maddr;
rman_res_t sdi_msize;

View File

@ -1,7 +1,11 @@
/*-
* Copyright (c) 2015 Landon Fuller <landon@landonf.org>
* Copyright (c) 2015-2016 Landon Fuller <landon@landonf.org>
* Copyright (c) 2017 The FreeBSD Foundation
* All rights reserved.
*
* Portions of this software were developed by Landon Fuller
* under sponsorship from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
@ -214,7 +218,7 @@ siba_write_ioctl(device_t dev, device_t child, uint16_t value, uint16_t mask)
/* Fetch CFG0 mapping */
dinfo = device_get_ivars(child);
if ((r = dinfo->cfg[0]) == NULL)
if ((r = dinfo->cfg_res[0]) == NULL)
return (ENODEV);
/* Mask and set TMSTATELOW core flag bits */
@ -266,7 +270,7 @@ siba_reset_hw(device_t dev, device_t child, uint16_t ioctl)
dinfo = device_get_ivars(child);
/* Can't suspend the core without access to the CFG0 registers */
if ((r = dinfo->cfg[0]) == NULL)
if ((r = dinfo->cfg_res[0]) == NULL)
return (ENODEV);
/* We require exclusive control over BHND_IOCTL_CLK_EN and
@ -338,7 +342,7 @@ siba_suspend_hw(device_t dev, device_t child)
pm = dinfo->pmu_info;
/* Can't suspend the core without access to the CFG0 registers */
if ((r = dinfo->cfg[0]) == NULL)
if ((r = dinfo->cfg_res[0]) == NULL)
return (ENODEV);
/* Already in RESET? */
@ -433,23 +437,26 @@ siba_read_config(device_t dev, device_t child, bus_size_t offset, void *value,
/* CFG0 registers must be available */
dinfo = device_get_ivars(child);
if (dinfo->cfg[0] == NULL)
if (dinfo->cfg_res[0] == NULL)
return (ENODEV);
/* Offset must fall within CFG0 */
r_size = rman_get_size(dinfo->cfg[0]->res);
r_size = rman_get_size(dinfo->cfg_res[0]->res);
if (r_size < offset || r_size - offset < width)
return (EFAULT);
switch (width) {
case 1:
*((uint8_t *)value) = bhnd_bus_read_1(dinfo->cfg[0], offset);
*((uint8_t *)value) = bhnd_bus_read_1(dinfo->cfg_res[0],
offset);
return (0);
case 2:
*((uint16_t *)value) = bhnd_bus_read_2(dinfo->cfg[0], offset);
*((uint16_t *)value) = bhnd_bus_read_2(dinfo->cfg_res[0],
offset);
return (0);
case 4:
*((uint32_t *)value) = bhnd_bus_read_4(dinfo->cfg[0], offset);
*((uint32_t *)value) = bhnd_bus_read_4(dinfo->cfg_res[0],
offset);
return (0);
default:
return (EINVAL);
@ -470,7 +477,7 @@ siba_write_config(device_t dev, device_t child, bus_size_t offset,
/* CFG0 registers must be available */
dinfo = device_get_ivars(child);
if ((r = dinfo->cfg[0]) == NULL)
if ((r = dinfo->cfg_res[0]) == NULL)
return (ENODEV);
/* Offset must fall within CFG0 */
@ -504,7 +511,7 @@ siba_get_port_count(device_t dev, device_t child, bhnd_port_type type)
type));
dinfo = device_get_ivars(child);
return (siba_addrspace_port_count(dinfo->core_id.num_addrspace));
return (siba_port_count(&dinfo->core_id, type));
}
static u_int
@ -519,11 +526,7 @@ siba_get_region_count(device_t dev, device_t child, bhnd_port_type type,
type, port));
dinfo = device_get_ivars(child);
if (!siba_is_port_valid(dinfo->core_id.num_addrspace, type, port))
return (0);
return (siba_addrspace_region_count(dinfo->core_id.num_addrspace,
port));
return (siba_port_region_count(&dinfo->core_id, type, port));
}
static int
@ -532,6 +535,7 @@ siba_get_port_rid(device_t dev, device_t child, bhnd_port_type port_type,
{
struct siba_devinfo *dinfo;
struct siba_addrspace *addrspace;
struct siba_cfg_block *cfg;
/* delegate non-bus-attached devices to our parent */
if (device_get_parent(child) != dev)
@ -539,11 +543,19 @@ siba_get_port_rid(device_t dev, device_t child, bhnd_port_type port_type,
port_type, port_num, region_num));
dinfo = device_get_ivars(child);
addrspace = siba_find_addrspace(dinfo, port_type, port_num, region_num);
if (addrspace == NULL)
return (-1);
/* Look for a matching addrspace entry */
addrspace = siba_find_addrspace(dinfo, port_type, port_num, region_num);
if (addrspace != NULL)
return (addrspace->sa_rid);
/* Try the config blocks */
cfg = siba_find_cfg_block(dinfo, port_type, port_num, region_num);
if (cfg != NULL)
return (cfg->cb_rid);
/* Not found */
return (-1);
}
static int
@ -563,13 +575,25 @@ siba_decode_port_rid(device_t dev, device_t child, int type, int rid,
if (type != SYS_RES_MEMORY)
return (EINVAL);
for (int i = 0; i < dinfo->core_id.num_addrspace; i++) {
/* Look for a matching addrspace entry */
for (u_int i = 0; i < dinfo->core_id.num_addrspace; i++) {
if (dinfo->addrspace[i].sa_rid != rid)
continue;
*port_type = BHND_PORT_DEVICE;
*port_num = siba_addrspace_port(i);
*region_num = siba_addrspace_region(i);
*port_num = siba_addrspace_device_port(i);
*region_num = siba_addrspace_device_region(i);
return (0);
}
/* Try the config blocks */
for (u_int i = 0; i < dinfo->core_id.num_cfg_blocks; i++) {
if (dinfo->cfg[i].cb_rid != rid)
continue;
*port_type = BHND_PORT_AGENT;
*port_num = siba_cfg_agent_port(i);
*region_num = siba_cfg_agent_region(i);
return (0);
}
@ -583,6 +607,7 @@ siba_get_region_addr(device_t dev, device_t child, bhnd_port_type port_type,
{
struct siba_devinfo *dinfo;
struct siba_addrspace *addrspace;
struct siba_cfg_block *cfg;
/* delegate non-bus-attached devices to our parent */
if (device_get_parent(child) != dev) {
@ -591,22 +616,31 @@ siba_get_region_addr(device_t dev, device_t child, bhnd_port_type port_type,
}
dinfo = device_get_ivars(child);
addrspace = siba_find_addrspace(dinfo, port_type, port_num, region_num);
if (addrspace == NULL)
return (ENOENT);
/* Look for a matching addrspace */
addrspace = siba_find_addrspace(dinfo, port_type, port_num, region_num);
if (addrspace != NULL) {
*addr = addrspace->sa_base;
*size = addrspace->sa_size - addrspace->sa_bus_reserved;
return (0);
}
/* Look for a matching cfg block */
cfg = siba_find_cfg_block(dinfo, port_type, port_num, region_num);
if (cfg != NULL) {
*addr = cfg->cb_base;
*size = cfg->cb_size;
return (0);
}
/* Not found */
return (ENOENT);
}
/**
* Default siba(4) bus driver implementation of BHND_BUS_GET_INTR_COUNT().
*
* This implementation consults @p child's configuration block mapping,
* returning SIBA_CORE_NUM_INTR if a valid CFG0 block is mapped.
*/
int
u_int
siba_get_intr_count(device_t dev, device_t child)
{
struct siba_devinfo *dinfo;
@ -616,42 +650,38 @@ siba_get_intr_count(device_t dev, device_t child)
return (BHND_BUS_GET_INTR_COUNT(device_get_parent(dev), child));
dinfo = device_get_ivars(child);
/* We can get/set interrupt sbflags on any core with a valid cfg0
* block; whether the core actually makes use of it is another matter
* entirely */
if (dinfo->cfg[0] == NULL)
if (!dinfo->intr_en) {
/* No interrupts */
return (0);
return (SIBA_CORE_NUM_INTR);
} else {
/* One assigned interrupt */
return (1);
}
}
/**
* Default siba(4) bus driver implementation of BHND_BUS_GET_CORE_IVEC().
*
* This implementation consults @p child's CFG0 register block,
* returning the interrupt flag assigned to @p child.
* Default siba(4) bus driver implementation of BHND_BUS_GET_INTR_IVEC().
*/
int
siba_get_core_ivec(device_t dev, device_t child, u_int intr, uint32_t *ivec)
siba_get_intr_ivec(device_t dev, device_t child, u_int intr, u_int *ivec)
{
struct siba_devinfo *dinfo;
uint32_t tpsflag;
/* delegate non-bus-attached devices to our parent */
if (device_get_parent(child) != dev)
return (BHND_BUS_GET_CORE_IVEC(device_get_parent(dev), child,
return (BHND_BUS_GET_INTR_IVEC(device_get_parent(dev), child,
intr, ivec));
/* Must be a valid interrupt ID */
if (intr >= siba_get_intr_count(dev, child))
return (ENXIO);
/* Fetch sbflag number */
dinfo = device_get_ivars(child);
tpsflag = bhnd_bus_read_4(dinfo->cfg[0], SIBA_CFG0_TPSFLAG);
*ivec = SIBA_REG_GET(tpsflag, TPS_NUM0);
KASSERT(intr == 0, ("invalid ivec %u", intr));
dinfo = device_get_ivars(child);
KASSERT(dinfo->intr_en, ("core does not have an interrupt assigned"));
*ivec = dinfo->intr.flag;
return (0);
}
@ -715,6 +745,55 @@ siba_register_addrspaces(device_t dev, struct siba_devinfo *di,
return (0);
}
/**
* Register all interrupt descriptors for @p dinfo. Must be called after
* configuration blocks have been mapped.
*
* @param dev The siba bus device.
* @param child The siba child device.
* @param dinfo The device info instance on which to register all interrupt
* descriptor entries.
* @param r A resource mapping the enumeration table block for @p di.
*/
static int
siba_register_interrupts(device_t dev, device_t child,
struct siba_devinfo *dinfo, struct bhnd_resource *r)
{
uint32_t tpsflag;
int error;
/* Is backplane interrupt distribution enabled for this core? */
tpsflag = bhnd_bus_read_4(r, SB0_REG_ABS(SIBA_CFG0_TPSFLAG));
if ((tpsflag & SIBA_TPS_F0EN0) == 0) {
dinfo->intr_en = false;
return (0);
}
/* Have one interrupt */
dinfo->intr_en = true;
dinfo->intr.flag = SIBA_REG_GET(tpsflag, TPS_NUM0);
dinfo->intr.mapped = false;
dinfo->intr.irq = 0;
dinfo->intr.rid = -1;
/* Map the interrupt */
error = BHND_BUS_MAP_INTR(dev, child, 0 /* single intr is always 0 */,
&dinfo->intr.irq);
if (error) {
device_printf(dev, "failed mapping interrupt line for core %u: "
"%d\n", dinfo->core_id.core_info.core_idx, error);
return (error);
}
dinfo->intr.mapped = true;
/* Update the resource list */
dinfo->intr.rid = resource_list_add_next(&dinfo->resources, SYS_RES_IRQ,
dinfo->intr.irq, dinfo->intr.irq, 1);
return (0);
}
/**
* Map per-core configuration blocks for @p dinfo.
*
@ -728,6 +807,7 @@ siba_map_cfg_resources(device_t dev, struct siba_devinfo *dinfo)
struct siba_addrspace *addrspace;
rman_res_t r_start, r_count, r_end;
uint8_t num_cfg;
int rid;
num_cfg = dinfo->core_id.num_cfg_blocks;
if (num_cfg > SIBA_MAX_CFG) {
@ -747,22 +827,28 @@ siba_map_cfg_resources(device_t dev, struct siba_devinfo *dinfo)
* Map the per-core configuration blocks
*/
for (uint8_t i = 0; i < num_cfg; i++) {
/* Determine the config block's address range; configuration
* blocks are allocated starting at SIBA_CFG0_OFFSET,
* growing downwards. */
r_start = addrspace->sa_base + SIBA_CFG0_OFFSET;
r_start -= i * SIBA_CFG_SIZE;
/* Add to child's resource list */
r_start = addrspace->sa_base + SIBA_CFG_OFFSET(i);
r_count = SIBA_CFG_SIZE;
r_end = r_start + r_count - 1;
/* Allocate the config resource */
dinfo->cfg_rid[i] = SIBA_CFG_RID(dinfo, i);
dinfo->cfg[i] = BHND_BUS_ALLOC_RESOURCE(dev, dev,
SYS_RES_MEMORY, &dinfo->cfg_rid[i], r_start, r_end,
r_count, RF_ACTIVE);
rid = resource_list_add_next(&dinfo->resources, SYS_RES_MEMORY,
r_start, r_end, r_count);
if (dinfo->cfg[i] == NULL) {
/* Initialize config block descriptor */
dinfo->cfg[i] = ((struct siba_cfg_block) {
.cb_base = r_start,
.cb_size = SIBA_CFG_SIZE,
.cb_rid = rid
});
/* Map the config resource for bus-level access */
dinfo->cfg_rid[i] = SIBA_CFG_RID(dinfo, i);
dinfo->cfg_res[i] = BHND_BUS_ALLOC_RESOURCE(dev, dev,
SYS_RES_MEMORY, &dinfo->cfg_rid[i], r_start, r_end,
r_count, RF_ACTIVE|RF_SHAREABLE);
if (dinfo->cfg_res[i] == NULL) {
device_printf(dev, "failed to allocate SIBA_CFG%hhu\n",
i);
return (ENXIO);
@ -805,7 +891,7 @@ siba_child_deleted(device_t dev, device_t child)
/* Free siba device info */
if ((dinfo = device_get_ivars(child)) != NULL)
siba_free_dinfo(dev, dinfo);
siba_free_dinfo(dev, child, dinfo);
device_set_ivars(child, NULL);
}
@ -846,6 +932,7 @@ siba_add_children(device_t dev)
*/
for (u_int i = 0; i < chipid->ncores; i++) {
struct siba_devinfo *dinfo;
device_t child;
uint32_t idhigh, idlow;
rman_res_t r_count, r_end, r_start;
@ -878,14 +965,16 @@ siba_add_children(device_t dev)
}
/* Add the child device */
children[i] = BUS_ADD_CHILD(dev, 0, NULL, -1);
if (children[i] == NULL) {
child = BUS_ADD_CHILD(dev, 0, NULL, -1);
if (child == NULL) {
error = ENXIO;
goto failed;
}
children[i] = child;
/* Initialize per-device bus info */
if ((dinfo = device_get_ivars(children[i])) == NULL) {
if ((dinfo = device_get_ivars(child)) == NULL) {
error = ENXIO;
goto failed;
}
@ -897,14 +986,18 @@ siba_add_children(device_t dev)
if ((error = siba_register_addrspaces(dev, dinfo, r)))
goto failed;
/* Register the core's interrupts */
if ((error = siba_register_interrupts(dev, child, dinfo, r)))
goto failed;
/* Unmap the core's register block */
bhnd_release_resource(dev, SYS_RES_MEMORY, rid, r);
r = NULL;
/* If pins are floating or the hardware is otherwise
* unpopulated, the device shouldn't be used. */
if (bhnd_is_hw_disabled(children[i]))
device_disable(children[i]);
if (bhnd_is_hw_disabled(child))
device_disable(child);
}
/* Map all valid core's config register blocks and perform interrupt
@ -912,7 +1005,6 @@ siba_add_children(device_t dev)
for (u_int i = 0; i < chipid->ncores; i++) {
struct siba_devinfo *dinfo;
device_t child;
int nintr;
child = children[i];
@ -926,16 +1018,6 @@ siba_add_children(device_t dev)
if ((error = siba_map_cfg_resources(dev, dinfo)))
goto failed;
/* Assign interrupts */
nintr = bhnd_get_intr_count(child);
for (int rid = 0; rid < nintr; rid++) {
error = BHND_BUS_ASSIGN_INTR(dev, child, rid);
if (error) {
device_printf(dev, "failed to assign interrupt "
"%d to core %u: %d\n", rid, i, error);
}
}
/* Issue bus callback for fully initialized child. */
BHND_BUS_CHILD_ADDED(dev, child);
}
@ -993,7 +1075,7 @@ static device_method_t siba_methods[] = {
DEVMETHOD(bhnd_bus_decode_port_rid, siba_decode_port_rid),
DEVMETHOD(bhnd_bus_get_region_addr, siba_get_region_addr),
DEVMETHOD(bhnd_bus_get_intr_count, siba_get_intr_count),
DEVMETHOD(bhnd_bus_get_core_ivec, siba_get_core_ivec),
DEVMETHOD(bhnd_bus_get_intr_ivec, siba_get_intr_ivec),
DEVMETHOD_END
};

View File

@ -51,20 +51,17 @@ __FBSDID("$FreeBSD$");
* Supports attachment of siba(4) bus devices via a bhndb bridge.
*/
//
// TODO: PCI rev < 6 interrupt handling
//
// On early PCI cores (rev < 6) interrupt masking is handled via interconnect
// configuration registers (SBINTVEC), rather than the PCI_INT_MASK
// config register.
//
// On those devices, we should handle interrupts locally using SBINTVEC, rather
// than delegating to our parent bhndb device.
//
struct siba_bhndb_softc;
static int siba_bhndb_wars_hwup(struct siba_softc *sc);
static int siba_bhndb_wars_hwup(struct siba_bhndb_softc *sc);
/* Bridge-specific core device quirks */
/* siba_bhndb per-instance state */
struct siba_bhndb_softc {
struct siba_softc siba; /**< common siba per-instance state */
uint32_t quirks; /**< bus-level quirks */
};
/* siba_bhndb quirks */
enum {
/** When PCIe-bridged, the D11 core's initiator request
* timeout must be disabled to prevent D11 from entering a
@ -72,14 +69,22 @@ enum {
SIBA_QUIRK_PCIE_D11_SB_TIMEOUT = (1<<0)
};
static struct bhnd_device_quirk bridge_quirks[] = {
/* Bus-level quirks when bridged via a PCI host bridge core */
static struct bhnd_device_quirk pci_bridge_quirks[] = {
BHND_DEVICE_QUIRK_END
};
/* Bus-level quirks when bridged via a PCIe host bridge core */
static struct bhnd_device_quirk pcie_bridge_quirks[] = {
BHND_CHIP_QUIRK (4311, HWREV_EQ(2), SIBA_QUIRK_PCIE_D11_SB_TIMEOUT),
BHND_CHIP_QUIRK (4312, HWREV_EQ(0), SIBA_QUIRK_PCIE_D11_SB_TIMEOUT),
BHND_DEVICE_QUIRK_END
};
/* Bus-level quirks specific to a particular host bridge core */
static struct bhnd_device bridge_devs[] = {
BHND_DEVICE(BCM, PCI, NULL, bridge_quirks),
BHND_DEVICE(BCM, PCI, NULL, pci_bridge_quirks),
BHND_DEVICE(BCM, PCIE, NULL, pcie_bridge_quirks),
BHND_DEVICE_END
};
@ -107,15 +112,23 @@ siba_bhndb_probe(device_t dev)
static int
siba_bhndb_attach(device_t dev)
{
struct siba_softc *sc;
struct siba_bhndb_softc *sc;
device_t hostb;
int error;
sc = device_get_softc(dev);
sc->quirks = 0;
/* Perform initial attach and enumerate our children. */
if ((error = siba_attach(dev)))
goto failed;
/* Fetch bus-level quirks required by the host bridge core */
if ((hostb = bhnd_bus_find_hostb_device(dev)) != NULL) {
sc->quirks |= bhnd_device_quirks(hostb, bridge_devs,
sizeof(bridge_devs[0]));
}
/* Apply attach/resume workarounds before any child drivers attach */
if ((error = siba_bhndb_wars_hwup(sc)))
goto failed;
@ -134,7 +147,7 @@ siba_bhndb_attach(device_t dev)
static int
siba_bhndb_resume(device_t dev)
{
struct siba_softc *sc;
struct siba_bhndb_softc *sc;
int error;
sc = device_get_softc(dev);
@ -151,11 +164,11 @@ siba_bhndb_resume(device_t dev)
static void
siba_bhndb_suspend_cfgblocks(device_t dev, struct siba_devinfo *dinfo) {
for (u_int i = 0; i < dinfo->core_id.num_cfg_blocks; i++) {
if (dinfo->cfg[i] == NULL)
if (dinfo->cfg_res[i] == NULL)
continue;
BHNDB_SUSPEND_RESOURCE(device_get_parent(dev), dev,
SYS_RES_MEMORY, dinfo->cfg[i]->res);
SYS_RES_MEMORY, dinfo->cfg_res[i]->res);
}
}
@ -196,11 +209,11 @@ siba_bhndb_resume_child(device_t dev, device_t child)
/* Resume all resource references to the child's config registers */
for (u_int i = 0; i < dinfo->core_id.num_cfg_blocks; i++) {
if (dinfo->cfg[i] == NULL)
if (dinfo->cfg_res[i] == NULL)
continue;
error = BHNDB_RESUME_RESOURCE(device_get_parent(dev), dev,
SYS_RES_MEMORY, dinfo->cfg[i]->res);
SYS_RES_MEMORY, dinfo->cfg_res[i]->res);
if (error) {
siba_bhndb_suspend_cfgblocks(dev, dinfo);
return (error);
@ -218,22 +231,14 @@ siba_bhndb_resume_child(device_t dev, device_t child)
/* Work-around implementation for SIBA_QUIRK_PCIE_D11_SB_TIMEOUT */
static int
siba_bhndb_wars_pcie_clear_d11_timeout(struct siba_softc *sc)
siba_bhndb_wars_pcie_clear_d11_timeout(struct siba_bhndb_softc *sc)
{
struct siba_devinfo *dinfo;
device_t hostb_dev;
device_t d11;
uint32_t imcfg;
/* Only applies when bridged by PCIe */
if ((hostb_dev = bhnd_bus_find_hostb_device(sc->dev)) == NULL)
return (ENXIO);
if (bhnd_get_class(hostb_dev) != BHND_DEVCLASS_PCIE)
return (0);
/* Only applies if there's a D11 core */
d11 = bhnd_bus_match_child(sc->dev, &(struct bhnd_core_match) {
/* Only applicable if there's a D11 core */
d11 = bhnd_bus_match_child(sc->siba.dev, &(struct bhnd_core_match) {
BHND_MATCH_CORE(BHND_MFGID_BCM, BHND_COREID_D11),
BHND_MATCH_CORE_UNIT(0)
});
@ -242,12 +247,12 @@ siba_bhndb_wars_pcie_clear_d11_timeout(struct siba_softc *sc)
/* Clear initiator timeout in D11's CFG0 block */
dinfo = device_get_ivars(d11);
KASSERT(dinfo->cfg[0] != NULL, ("missing core config mapping"));
KASSERT(dinfo->cfg_res[0] != NULL, ("missing core config mapping"));
imcfg = bhnd_bus_read_4(dinfo->cfg[0], SIBA_CFG0_IMCONFIGLOW);
imcfg = bhnd_bus_read_4(dinfo->cfg_res[0], SIBA_CFG0_IMCONFIGLOW);
imcfg &= ~SIBA_IMCL_RTO_MASK;
bhnd_bus_write_4(dinfo->cfg[0], SIBA_CFG0_IMCONFIGLOW, imcfg);
bhnd_bus_write_4(dinfo->cfg_res[0], SIBA_CFG0_IMCONFIGLOW, imcfg);
return (0);
}
@ -257,19 +262,11 @@ siba_bhndb_wars_pcie_clear_d11_timeout(struct siba_softc *sc)
* of the bus.
*/
static int
siba_bhndb_wars_hwup(struct siba_softc *sc)
siba_bhndb_wars_hwup(struct siba_bhndb_softc *sc)
{
device_t hostb_dev;
uint32_t quirks;
int error;
if ((hostb_dev = bhnd_bus_find_hostb_device(sc->dev)) == NULL)
return (ENXIO);
quirks = bhnd_device_quirks(hostb_dev, bridge_devs,
sizeof(bridge_devs[0]));
if (quirks & SIBA_QUIRK_PCIE_D11_SB_TIMEOUT) {
if (sc->quirks & SIBA_QUIRK_PCIE_D11_SB_TIMEOUT) {
if ((error = siba_bhndb_wars_pcie_clear_d11_timeout(sc)))
return (error);
}

View File

@ -334,7 +334,8 @@ siba_erom_lookup_core_addr(bhnd_erom_t *erom, const struct bhnd_core_match *desc
struct siba_core_id sid;
uint32_t am, am_addr, am_size;
u_int am_offset;
u_int addrspace;
u_int addrspace, cfg;
int error;
sc = (struct siba_erom *)erom;
@ -347,16 +348,64 @@ siba_erom_lookup_core_addr(bhnd_erom_t *erom, const struct bhnd_core_match *desc
sid = siba_eio_read_core_id(&sc->io, core.core_idx, core.unit);
/* Is port valid? */
if (!siba_is_port_valid(sid.num_addrspace, type, port))
if (!siba_is_port_valid(&sid, type, port))
return (ENOENT);
/* Is region valid? */
if (region >= siba_addrspace_region_count(sid.num_addrspace, port))
if (region >= siba_port_region_count(&sid, type, port))
return (ENOENT);
/* Map the bhnd port values to a siba addrspace index */
error = siba_addrspace_index(sid.num_addrspace, type, port, region,
&addrspace);
/* Is this a siba configuration region? If so, this is mapped to an
* offset within the device0.0 port */
error = siba_cfg_index(&sid, type, port, region, &cfg);
if (!error) {
bhnd_addr_t region_addr;
bhnd_addr_t region_size;
bhnd_size_t cfg_offset, cfg_size;
cfg_offset = SIBA_CFG_OFFSET(cfg);
cfg_size = SIBA_CFG_SIZE;
/* Fetch the device0.0 addr/size */
error = siba_erom_lookup_core_addr(erom, desc, BHND_PORT_DEVICE,
0, 0, NULL, &region_addr, &region_size);
if (error)
return (error);
/* Verify that our offset fits within the region */
if (region_size < cfg_size) {
printf("%s%u.%u offset %ju exceeds %s0.0 size %ju\n",
bhnd_port_type_name(type), port, region, cfg_offset,
bhnd_port_type_name(BHND_PORT_DEVICE), region_size);
return (ENXIO);
}
if (BHND_ADDR_MAX - region_addr < cfg_offset) {
printf("%s%u.%u offset %ju would overflow %s0.0 addr "
"%ju\n", bhnd_port_type_name(type), port, region,
cfg_offset, bhnd_port_type_name(BHND_PORT_DEVICE),
region_addr);
return (ENXIO);
}
if (info != NULL)
*info = core;
*addr = region_addr + cfg_offset;
*size = cfg_size;
return (0);
}
/*
* Otherwise, must be a device port.
*
* Map the bhnd device port to a siba addrspace index. Unlike siba(4)
* bus drivers, we do not exclude the siba(4) configuration blocks from
* the first device port.
*/
error = siba_addrspace_index(&sid, type, port, region, &addrspace);
if (error)
return (error);

View File

@ -1,7 +1,11 @@
/*-
* Copyright (c) 2015 Landon Fuller <landon@landonf.org>
* Copyright (c) 2015-2016 Landon Fuller <landon@landonf.org>
* Copyright (c) 2017 The FreeBSD Foundation
* All rights reserved.
*
* Portions of this software were developed by Landon Fuller
* under sponsorship from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
@ -122,12 +126,19 @@ siba_alloc_dinfo(device_t bus)
return NULL;
for (u_int i = 0; i < nitems(dinfo->cfg); i++) {
dinfo->cfg[i] = NULL;
dinfo->cfg[i] = ((struct siba_cfg_block){
.cb_base = 0,
.cb_size = 0,
.cb_rid = -1,
});
dinfo->cfg_res[i] = NULL;
dinfo->cfg_rid[i] = -1;
}
resource_list_init(&dinfo->resources);
dinfo->intr_en = false;
return dinfo;
}
@ -151,12 +162,13 @@ siba_init_dinfo(device_t dev, struct siba_devinfo *dinfo,
}
/**
* Map an addrspace index to its corresponding bhnd(4) port number.
* Map an addrspace index to its corresponding bhnd(4) BHND_PORT_DEVICE port
* number.
*
* @param addrspace Address space index.
*/
u_int
siba_addrspace_port(u_int addrspace)
siba_addrspace_device_port(u_int addrspace)
{
/* The first addrspace is always mapped to device0; the remainder
* are mapped to device1 */
@ -167,12 +179,13 @@ siba_addrspace_port(u_int addrspace)
}
/**
* Map an addrspace index to its corresponding bhnd(4) region number.
* Map an addrspace index to its corresponding bhnd(4) BHND_PORT_DEVICE port
* region number.
*
* @param addrspace Address space index.
*/
u_int
siba_addrspace_region(u_int addrspace)
siba_addrspace_device_region(u_int addrspace)
{
/* The first addrspace is always mapped to device0.0; the remainder
* are mapped to device1.0 + (n - 1) */
@ -183,65 +196,196 @@ siba_addrspace_region(u_int addrspace)
}
/**
* Return the number of bhnd(4) ports to advertise for the given
* @p num_addrspace.
* Map an config block index to its corresponding bhnd(4) BHND_PORT_AGENT port
* number.
*
* @param num_addrspace The number of siba address spaces.
* @param cfg Config block index.
*/
u_int
siba_addrspace_port_count(u_int num_addrspace)
siba_cfg_agent_port(u_int cfg)
{
/* 0, 1, or 2 ports */
return min(num_addrspace, 2);
}
/**
* Return the number of bhnd(4) regions to advertise on @p port
* given the provided @p num_addrspace address space count.
*
* @param num_addrspace The number of core-mapped siba(4) Sonics/OCP address
* spaces.
*/
u_int
siba_addrspace_region_count(u_int num_addrspace, u_int port)
{
/* The first address space, if any, is mapped to device0.0 */
if (port == 0)
return (min(num_addrspace, 1));
/* All remaining address spaces are mapped to device0.(n - 1) */
if (port == 1 && num_addrspace >= 2)
return (num_addrspace - 1);
/* No region mapping */
/* Always agent0 */
return (0);
}
/**
* Return true if @p port is defined given an address space count
* of @p num_addrspace, false otherwise.
* Map an config block index to its corresponding bhnd(4) BHND_PORT_AGENT port
* region number.
*
* Refer to the siba_find_addrspace() function for information on siba's
* mapping of bhnd(4) port and region identifiers.
* @param cfg Config block index.
*/
u_int
siba_cfg_agent_region(u_int cfg)
{
/* Always agent0.<idx> */
return (cfg);
}
/**
* Return the number of bhnd(4) ports to advertise for the given
* @p core_id and @p port_type.
*
* @param num_addrspace The number of address spaces to verify the port against.
* @param type The bhnd(4) port type.
* Refer to the siba_addrspace_index() and siba_cfg_index() functions for
* information on siba's mapping of bhnd(4) port and region identifiers.
*
* @param core_id The siba core info.
* @param port_type The bhnd(4) port type.
*/
u_int
siba_port_count(struct siba_core_id *core_id, bhnd_port_type port_type)
{
switch (port_type) {
case BHND_PORT_DEVICE:
/* 0, 1, or 2 ports */
return (min(core_id->num_addrspace, 2));
case BHND_PORT_AGENT:
/* One agent port maps all configuration blocks */
if (core_id->num_cfg_blocks > 0)
return (1);
/* Do not advertise an agent port if there are no configuration
* register blocks */
return (0);
default:
return (0);
}
}
/**
* Return true if @p port of @p port_type is defined by @p core_id, false
* otherwise.
*
* @param core_id The siba core info.
* @param port_type The bhnd(4) port type.
* @param port The bhnd(4) port number.
*/
bool
siba_is_port_valid(u_int num_addrspace, bhnd_port_type type, u_int port)
siba_is_port_valid(struct siba_core_id *core_id, bhnd_port_type port_type,
u_int port)
{
/* Only device ports are supported */
if (type != BHND_PORT_DEVICE)
return (false);
/* Verify the index against the port count */
if (siba_addrspace_port_count(num_addrspace) <= port)
if (siba_port_count(core_id, port_type) <= port)
return (false);
return (true);
}
/**
* Return the number of bhnd(4) regions to advertise for @p core_id on the
* @p port of @p port_type.
*
* @param core_id The siba core info.
* @param port_type The bhnd(4) port type.
*/
u_int
siba_port_region_count(struct siba_core_id *core_id, bhnd_port_type port_type,
u_int port)
{
/* The port must exist */
if (!siba_is_port_valid(core_id, port_type, port))
return (0);
switch (port_type) {
case BHND_PORT_DEVICE:
/* The first address space, if any, is mapped to device0.0 */
if (port == 0)
return (min(core_id->num_addrspace, 1));
/* All remaining address spaces are mapped to device0.(n - 1) */
if (port == 1 && core_id->num_addrspace >= 2)
return (core_id->num_addrspace - 1);
break;
case BHND_PORT_AGENT:
/* All config blocks are mapped to a single port */
if (port == 0)
return (core_id->num_cfg_blocks);
break;
default:
break;
}
/* Validated above */
panic("siba_is_port_valid() returned true for unknown %s.%u port",
bhnd_port_type_name(port_type), port);
}
/**
* Map a bhnd(4) type/port/region triplet to its associated config block index,
* if any.
*
* We map config registers to port/region identifiers as follows:
*
* [port].[region] [cfg register block]
* agent0.0 0
* agent0.1 1
*
* @param num_addrspace The number of available siba address spaces.
* @param port_type The bhnd(4) port type.
* @param port The bhnd(4) port number.
* @param region The bhnd(4) port region.
* @param addridx On success, the corresponding addrspace index.
*
* @retval 0 success
* @retval ENOENT if the given type/port/region cannot be mapped to a
* siba config register block.
*/
int
siba_cfg_index(struct siba_core_id *core_id, bhnd_port_type port_type,
u_int port, u_int region, u_int *cfgidx)
{
/* Config blocks are mapped to agent ports */
if (port_type != BHND_PORT_AGENT)
return (ENOENT);
/* Port must be valid */
if (!siba_is_port_valid(core_id, port_type, port))
return (ENOENT);
if (region >= core_id->num_cfg_blocks)
return (ENOENT);
if (region >= SIBA_MAX_CFG)
return (ENOENT);
/* Found */
*cfgidx = region;
return (0);
}
/**
* Map an bhnd(4) type/port/region triplet to its associated config block
* entry, if any.
*
* The only supported port type is BHND_PORT_DEVICE.
*
* @param dinfo The device info to search for a matching address space.
* @param type The bhnd(4) port type.
* @param port The bhnd(4) port number.
* @param region The bhnd(4) port region.
*/
struct siba_cfg_block *
siba_find_cfg_block(struct siba_devinfo *dinfo, bhnd_port_type type, u_int port,
u_int region)
{
u_int cfgidx;
int error;
/* Map to addrspace index */
error = siba_cfg_index(&dinfo->core_id, type, port, region, &cfgidx);
if (error)
return (NULL);
/* Found */
return (&dinfo->cfg[cfgidx]);
}
/**
* Map a bhnd(4) type/port/region triplet to its associated address space
* index, if any.
@ -255,10 +399,8 @@ siba_is_port_valid(u_int num_addrspace, bhnd_port_type type, u_int port)
* device1.1 2
* device1.2 3
*
* The only supported port type is BHND_PORT_DEVICE.
*
* @param num_addrspace The number of available siba address spaces.
* @param type The bhnd(4) port type.
* @param core_id The siba core info.
* @param port_type The bhnd(4) port type.
* @param port The bhnd(4) port number.
* @param region The bhnd(4) port region.
* @param addridx On success, the corresponding addrspace index.
@ -268,12 +410,17 @@ siba_is_port_valid(u_int num_addrspace, bhnd_port_type type, u_int port)
* siba address space.
*/
int
siba_addrspace_index(u_int num_addrspace, bhnd_port_type type, u_int port,
u_int region, u_int *addridx)
siba_addrspace_index(struct siba_core_id *core_id, bhnd_port_type port_type,
u_int port, u_int region, u_int *addridx)
{
u_int idx;
if (!siba_is_port_valid(num_addrspace, type, port))
/* Address spaces are always device ports */
if (port_type != BHND_PORT_DEVICE)
return (ENOENT);
/* Port must be valid */
if (!siba_is_port_valid(core_id, port_type, port))
return (ENOENT);
if (port == 0)
@ -283,7 +430,7 @@ siba_addrspace_index(u_int num_addrspace, bhnd_port_type type, u_int port,
else
return (ENOENT);
if (idx >= num_addrspace)
if (idx >= core_id->num_addrspace)
return (ENOENT);
/* Found */
@ -310,8 +457,8 @@ siba_find_addrspace(struct siba_devinfo *dinfo, bhnd_port_type type, u_int port,
int error;
/* Map to addrspace index */
error = siba_addrspace_index(dinfo->core_id.num_addrspace, type, port,
region, &addridx);
error = siba_addrspace_index(&dinfo->core_id, type, port, region,
&addridx);
if (error)
return (NULL);
@ -377,25 +524,32 @@ siba_append_dinfo_region(struct siba_devinfo *dinfo, uint8_t addridx,
* Deallocate the given device info structure and any associated resources.
*
* @param dev The requesting bus device.
* @param dinfo Device info to be deallocated.
* @param child The siba child device.
* @param dinfo Device info associated with @p child to be deallocated.
*/
void
siba_free_dinfo(device_t dev, struct siba_devinfo *dinfo)
siba_free_dinfo(device_t dev, device_t child, struct siba_devinfo *dinfo)
{
resource_list_free(&dinfo->resources);
/* Free all mapped configuration blocks */
for (u_int i = 0; i < nitems(dinfo->cfg); i++) {
if (dinfo->cfg[i] == NULL)
if (dinfo->cfg_res[i] == NULL)
continue;
bhnd_release_resource(dev, SYS_RES_MEMORY, dinfo->cfg_rid[i],
dinfo->cfg[i]);
dinfo->cfg_res[i]);
dinfo->cfg[i] = NULL;
dinfo->cfg_res[i] = NULL;
dinfo->cfg_rid[i] = -1;
}
/* Unmap the core's interrupt */
if (dinfo->intr_en && dinfo->intr.mapped) {
BHND_BUS_UNMAP_INTR(dev, child, dinfo->intr.irq);
dinfo->intr.mapped = false;
}
free(dinfo, M_BHND);
}
@ -490,7 +644,7 @@ siba_write_target_state(device_t dev, struct siba_devinfo *dinfo,
uint32_t rval;
/* Must have a CFG0 block */
if ((r = dinfo->cfg[0]) == NULL)
if ((r = dinfo->cfg_res[0]) == NULL)
return (ENODEV);
/* Verify the register offset falls within CFG register block */
@ -535,7 +689,7 @@ siba_wait_target_busy(device_t dev, struct siba_devinfo *dinfo, int usec)
struct bhnd_resource *r;
uint32_t ts_high;
if ((r = dinfo->cfg[0]) == NULL)
if ((r = dinfo->cfg_res[0]) == NULL)
return (ENODEV);
for (int i = 0; i < usec; i += 10) {

View File

@ -48,7 +48,7 @@
#define SIBA_ENUM_ADDR BHND_DEFAULT_CHIPC_ADDR /**< enumeration space */
#define SIBA_ENUM_SIZE 0x00100000 /**< size of the enumeration space */
#define SIBA_CORE_SIZE BHND_DEFAULT_CORE_SIZE /**< per-core register block size */
#define SIBA_CORE_NUM_INTR 1 /**< number of per-core interrupt lines */
#define SIBA_MAX_INTR 32 /**< maximum number of backplane interrupt vectors */
#define SIBA_MAX_CORES \
(SIBA_ENUM_SIZE/SIBA_CORE_SIZE) /**< Maximum number of cores */
@ -70,9 +70,13 @@
#define SIBA_CFG0_OFFSET 0xf00 /**< first configuration block */
#define SIBA_CFG1_OFFSET 0xe00 /**< second configuration block (sonics >= 2.3) */
#define SIBA_CFG_SIZE 0x100 /**< cfg register block size */
/* Return the SIBA_CORE_ADDR-relative offset for the given siba configuration
* register block; configuration blocks are allocated starting at
* SIBA_CFG0_OFFSET, growing downwards. */
#define SIBA_CFG_OFFSET(_n) (SIBA_CFG0_OFFSET - ((_n) * SIBA_CFG_SIZE))
/* Return the SIBA_CORE_ADDR-relative offset for a SIBA_CFG* register. */
#define SB0_REG_ABS(off) ((off) + SIBA_CFG0_OFFSET)
#define SB1_REG_ABS(off) ((off) + SIBA_CFG1_OFFSET)
@ -118,6 +122,9 @@
#define SIBA_IPS_INT4_MASK 0x3f000000 /* which sbflags get routed to mips interrupt 4 */
#define SIBA_IPS_INT4_SHIFT 24
#define SIBA_IPS_INT_SHIFT(_i) ((_i - 1) * 8)
#define SIBA_IPS_INT_MASK(_i) (SIBA_IPS_INT1_MASK << SIBA_IPS_INT_SHIFT(_i))
/* sbtpsflag */
#define SIBA_TPS_NUM0_MASK 0x3f /* interrupt sbFlag # generated by this core */
#define SIBA_TPS_NUM0_SHIFT 0

View File

@ -1,7 +1,11 @@
/*-
* Copyright (c) 2015 Landon Fuller <landon@landonf.org>
* Copyright (c) 2015-2016 Landon Fuller <landon@landonf.org>
* Copyright (c) 2017 The FreeBSD Foundation
* All rights reserved.
*
* Portions of this software were developed by Landon Fuller
* under sponsorship from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
@ -46,6 +50,7 @@
*/
struct siba_addrspace;
struct siba_cfg_block;
struct siba_devinfo;
struct siba_core_id;
@ -54,9 +59,9 @@ int siba_attach(device_t dev);
int siba_detach(device_t dev);
int siba_resume(device_t dev);
int siba_suspend(device_t dev);
int siba_get_intr_count(device_t dev, device_t child);
int siba_get_core_ivec(device_t dev, device_t child,
u_int intr, uint32_t *ivec);
u_int siba_get_intr_count(device_t dev, device_t child);
int siba_get_intr_ivec(device_t dev, device_t child,
u_int intr, u_int *ivec);
uint16_t siba_get_bhnd_mfgid(uint16_t ocp_vendor);
@ -69,24 +74,38 @@ struct siba_devinfo *siba_alloc_dinfo(device_t dev);
int siba_init_dinfo(device_t dev,
struct siba_devinfo *dinfo,
const struct siba_core_id *core_id);
void siba_free_dinfo(device_t dev,
void siba_free_dinfo(device_t dev, device_t child,
struct siba_devinfo *dinfo);
u_int siba_addrspace_port_count(u_int num_addrspace);
u_int siba_addrspace_region_count(u_int num_addrspace,
u_int port);
u_int siba_port_count(struct siba_core_id *core_id,
bhnd_port_type port_type);
bool siba_is_port_valid(struct siba_core_id *core_id,
bhnd_port_type port_type, u_int port);
u_int siba_addrspace_port(u_int addrspace);
u_int siba_addrspace_region(u_int addrspace);
int siba_addrspace_index(u_int num_addrspace,
u_int siba_port_region_count(
struct siba_core_id *core_id,
bhnd_port_type port_type, u_int port);
int siba_cfg_index(struct siba_core_id *core_id,
bhnd_port_type type, u_int port, u_int region,
u_int *cfgidx);
int siba_addrspace_index(struct siba_core_id *core_id,
bhnd_port_type type, u_int port, u_int region,
u_int *addridx);
bool siba_is_port_valid(u_int num_addrspace,
bhnd_port_type type, u_int port);
u_int siba_addrspace_device_port(u_int addrspace);
u_int siba_addrspace_device_region(u_int addrspace);
u_int siba_cfg_agent_port(u_int cfg);
u_int siba_cfg_agent_region(u_int cfg);
struct siba_addrspace *siba_find_addrspace(struct siba_devinfo *dinfo,
bhnd_port_type type, u_int port, u_int region);
struct siba_cfg_block *siba_find_cfg_block(struct siba_devinfo *dinfo,
bhnd_port_type type, u_int port, u_int region);
int siba_append_dinfo_region(struct siba_devinfo *dinfo,
uint8_t sid, uint32_t base, uint32_t size,
uint32_t bus_reserved);
@ -134,6 +153,21 @@ struct siba_addrspace {
* address space reserved for the bus */
};
/** siba(4) config block descriptor */
struct siba_cfg_block {
uint32_t cb_base; /**< base address */
uint32_t cb_size; /**< size */
int cb_rid; /**< bus resource id */
};
/** siba(4) backplane interrupt flag descriptor */
struct siba_intr {
u_int flag; /**< backplane flag # */
bool mapped; /**< if an irq has been mapped */
int rid; /**< bus resource id, or -1 if unassigned */
rman_res_t irq; /**< the mapped bus irq, if any */
};
/**
* siba(4) per-core identification info.
*/
@ -156,9 +190,12 @@ struct siba_devinfo {
struct resource_list resources; /**< per-core memory regions. */
struct siba_core_id core_id; /**< core identification info */
struct siba_addrspace addrspace[SIBA_MAX_ADDRSPACE]; /**< memory map descriptors */
struct siba_cfg_block cfg[SIBA_MAX_CFG]; /**< config block descriptors */
struct siba_intr intr; /**< interrupt flag descriptor, if any */
bool intr_en; /**< if true, core has an assigned interrupt flag */
struct bhnd_resource *cfg[SIBA_MAX_CFG]; /**< SIBA_CFG_* registers */
int cfg_rid[SIBA_MAX_CFG]; /**< SIBA_CFG_* resource IDs */
struct bhnd_resource *cfg_res[SIBA_MAX_CFG]; /**< bus-mapped config block registers */
int cfg_rid[SIBA_MAX_CFG]; /**< bus-mapped config block resource IDs */
struct bhnd_core_pmu_info *pmu_info; /**< Bus-managed PMU state, or NULL */
};

View File

@ -1,7 +1,11 @@
/*-
* Copyright (c) 2016 Landon Fuller <landonf@FreeBSD.org>
* Copyright (c) 2017 The FreeBSD Foundation
* All rights reserved.
*
* Portions of this software were developed by Landon Fuller
* under sponsorship from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
@ -34,13 +38,20 @@ __FBSDID("$FreeBSD$");
#include <sys/kernel.h>
#include <sys/bus.h>
#include <sys/module.h>
#include <sys/proc.h>
#include <machine/bus.h>
#include <sys/rman.h>
#include <machine/intr.h>
#include <machine/resource.h>
#include <dev/bhnd/bhnd.h>
#include <dev/bhnd/siba/sibareg.h>
#include "pic_if.h"
#include "bcm_mipsvar.h"
#include "bcm_bmipsreg.h"
/*
@ -50,74 +61,360 @@ __FBSDID("$FreeBSD$");
* us to assume the availability of siba interrupt registers.
*/
struct bcm_bmips_softc;
static int bcm_bmips_pic_intr(void *arg);
static void bcm_bmips_mask_irq(struct bcm_bmips_softc *sc, u_int mips_irq,
u_int ivec);
static void bcm_bmips_unmask_irq(struct bcm_bmips_softc *sc, u_int mips_irq,
u_int ivec);
static const struct bhnd_device bcm_bmips_devs[] = {
BHND_DEVICE(BCM, MIPS33, NULL, NULL, BHND_DF_SOC),
BHND_DEVICE_END
};
struct bcm_bmips_softc {
struct bcm_mips_softc bcm_mips; /**< parent softc */
device_t dev;
struct resource *mem_res;
struct resource *mem; /**< cpu core registers */
int mem_rid;
struct resource *cfg; /**< cpu core's cfg0 register block */
int cfg_rid;
};
#define BCM_BMIPS_NCPU_IRQS 5 /**< MIPS HW IRQs 0-4 are assignable */
#define BCM_BMIPS_TIMER_IRQ 5 /**< MIPS HW IRQ5 is always assigned to the timer */
static int
bcm_bmips_probe(device_t dev)
{
const struct bhnd_device *id;
id = bhnd_device_lookup(dev, bcm_bmips_devs,
sizeof(bcm_bmips_devs[0]));
id = bhnd_device_lookup(dev, bcm_bmips_devs, sizeof(bcm_bmips_devs[0]));
if (id == NULL)
return (ENXIO);
/* Check the chip type; should only be found on siba(4) chipsets */
if (bhnd_get_chipid(dev)->chip_type != BHND_CHIPTYPE_SIBA)
return (ENXIO);
bhnd_set_default_core_desc(dev);
return (BUS_PROBE_DEFAULT);
}
static int
bcm_bmips_attach(device_t dev)
{
struct bcm_bmips_softc *sc;
int error;
sc = device_get_softc(dev);
sc->dev = dev;
/* Allocate bus resources */
/* Allocate our core's register block */
sc->mem_rid = 0;
sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->mem_rid,
sc->mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->mem_rid,
RF_ACTIVE);
if (sc->mem_res == NULL)
return (ENXIO);
if (sc->mem == NULL) {
device_printf(dev, "failed to allocate cpu register block\n");
error = ENXIO;
goto failed;
}
/* Determine the resource ID for our siba CFG0 registers */
sc->cfg_rid = bhnd_get_port_rid(dev, BHND_PORT_AGENT, 0, 0);
if (sc->cfg_rid == -1) {
device_printf(dev, "missing required cfg0 register block\n");
error = ENXIO;
goto failed;
}
/* Allocate our CFG0 register block */
sc->cfg = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->cfg_rid,
RF_ACTIVE|RF_SHAREABLE);
if (sc->cfg == NULL) {
device_printf(dev, "failed to allocate cfg0 register block\n");
error = ENXIO;
goto failed;
}
/* Clear interrupt map */
bus_write_4(sc->cfg, SIBA_CFG0_INTVEC, 0x0); /* MIPS IRQ0 */
bus_write_4(sc->cfg, SIBA_CFG0_IPSFLAG, 0x0); /* MIPS IRQ1-4 */
/* Initialize the generic BHND MIPS driver state */
error = bcm_mips_attach(dev, BCM_BMIPS_NCPU_IRQS, BCM_BMIPS_TIMER_IRQ,
bcm_bmips_pic_intr);
if (error)
goto failed;
return (0);
failed:
if (sc->mem != NULL)
bus_release_resource(dev, SYS_RES_MEMORY, sc->mem_rid, sc->mem);
if (sc->cfg != NULL)
bus_release_resource(dev, SYS_RES_MEMORY, sc->cfg_rid, sc->cfg);
return (error);
}
static int
bcm_bmips_detach(device_t dev)
{
struct bcm_bmips_softc *sc;
int error;
sc = device_get_softc(dev);
bus_release_resource(dev, SYS_RES_MEMORY, sc->mem_rid, sc->mem_res);
if ((error = bcm_mips_detach(dev)))
return (error);
bus_release_resource(dev, SYS_RES_MEMORY, sc->mem_rid, sc->mem);
bus_release_resource(dev, SYS_RES_MEMORY, sc->cfg_rid, sc->cfg);
return (0);
}
/* PIC_DISABLE_INTR() */
static void
bcm_bmips_pic_disable_intr(device_t dev, struct intr_irqsrc *irqsrc)
{
struct bcm_bmips_softc *sc;
struct bcm_mips_irqsrc *isrc;
sc = device_get_softc(dev);
isrc = (struct bcm_mips_irqsrc *)irqsrc;
KASSERT(isrc->cpuirq != NULL, ("no assigned MIPS IRQ"));
bcm_bmips_mask_irq(sc, isrc->cpuirq->mips_irq, isrc->ivec);
}
/* PIC_ENABLE_INTR() */
static void
bcm_bmips_pic_enable_intr(device_t dev, struct intr_irqsrc *irqsrc)
{
struct bcm_bmips_softc *sc;
struct bcm_mips_irqsrc *isrc;
sc = device_get_softc(dev);
isrc = (struct bcm_mips_irqsrc *)irqsrc;
KASSERT(isrc->cpuirq != NULL, ("no assigned MIPS IRQ"));
bcm_bmips_unmask_irq(sc, isrc->cpuirq->mips_irq, isrc->ivec);
}
/* PIC_PRE_ITHREAD() */
static void
bcm_bmips_pic_pre_ithread(device_t dev, struct intr_irqsrc *isrc)
{
bcm_bmips_pic_disable_intr(dev, isrc);
}
/* PIC_POST_ITHREAD() */
static void
bcm_bmips_pic_post_ithread(device_t dev, struct intr_irqsrc *isrc)
{
bcm_bmips_pic_enable_intr(dev, isrc);
}
/* PIC_POST_FILTER() */
static void
bcm_bmips_pic_post_filter(device_t dev, struct intr_irqsrc *isrc)
{
}
/**
* Disable routing of backplane interrupt vector @p ivec to MIPS IRQ
* @p mips_irq.
*/
static void
bcm_bmips_mask_irq(struct bcm_bmips_softc *sc, u_int mips_irq, u_int ivec)
{
KASSERT(ivec < SIBA_MAX_INTR, ("invalid sbflag# ivec"));
KASSERT(mips_irq < sc->bcm_mips.num_cpuirqs, ("invalid MIPS IRQ %u",
mips_irq));
if (mips_irq == 0) {
uint32_t sbintvec;
sbintvec = bus_read_4(sc->cfg, SIBA_CFG0_INTVEC);
sbintvec &= ~(1 << ivec);
bus_write_4(sc->cfg, SIBA_CFG0_INTVEC, sbintvec);
} else {
uint32_t ipsflag;
/* Can we route this via ipsflag? */
KASSERT(((1 << ivec) & SIBA_IPS_INT1_MASK) != 0,
("cannot route high sbflag# ivec %u", ivec));
ipsflag = bus_read_4(sc->cfg, SIBA_CFG0_IPSFLAG);
ipsflag &= ~(
((1 << ivec) << SIBA_IPS_INT_SHIFT(mips_irq)) &
SIBA_IPS_INT_MASK(mips_irq));
bus_write_4(sc->cfg, SIBA_CFG0_IPSFLAG, ipsflag);
}
}
/**
* Enable routing of an interrupt.
*/
static void
bcm_bmips_unmask_irq(struct bcm_bmips_softc *sc, u_int mips_irq, u_int ivec)
{
KASSERT(ivec < SIBA_MAX_INTR, ("invalid sbflag# ivec"));
KASSERT(mips_irq < sc->bcm_mips.num_cpuirqs, ("invalid MIPS IRQ %u",
mips_irq));
if (mips_irq == 0) {
uint32_t sbintvec;
sbintvec = bus_read_4(sc->cfg, SIBA_CFG0_INTVEC);
sbintvec |= (1 << ivec);
bus_write_4(sc->cfg, SIBA_CFG0_INTVEC, sbintvec);
} else {
uint32_t ipsflag;
/* Can we route this via ipsflag? */
KASSERT(((1 << ivec) & SIBA_IPS_INT1_MASK) != 0,
("cannot route high sbflag# ivec %u", ivec));
ipsflag = bus_read_4(sc->cfg, SIBA_CFG0_IPSFLAG);
ipsflag |= (ivec << SIBA_IPS_INT_SHIFT(mips_irq)) &
SIBA_IPS_INT_MASK(mips_irq);
bus_write_4(sc->cfg, SIBA_CFG0_IPSFLAG, ipsflag);
}
}
/* our MIPS CPU interrupt filter */
static int
bcm_bmips_pic_intr(void *arg)
{
struct bcm_bmips_softc *sc;
struct bcm_mips_cpuirq *cpuirq;
struct bcm_mips_irqsrc *isrc_solo;
uint32_t sbintvec, sbstatus;
u_int mips_irq, i;
int error;
cpuirq = arg;
sc = (struct bcm_bmips_softc*)cpuirq->sc;
/* Fetch current interrupt state */
sbstatus = bus_read_4(sc->cfg, SIBA_CFG0_FLAGST);
/* Fetch mask of interrupt vectors routed to this MIPS IRQ */
mips_irq = cpuirq->mips_irq;
if (mips_irq == 0) {
sbintvec = bus_read_4(sc->cfg, SIBA_CFG0_INTVEC);
} else {
uint32_t ipsflag;
ipsflag = bus_read_4(sc->cfg, SIBA_CFG0_IPSFLAG);
/* Map to an intvec-compatible representation */
switch (mips_irq) {
case 1:
sbintvec = (ipsflag & SIBA_IPS_INT1_MASK) >>
SIBA_IPS_INT1_SHIFT;
break;
case 2:
sbintvec = (ipsflag & SIBA_IPS_INT2_MASK) >>
SIBA_IPS_INT2_SHIFT;
break;
case 3:
sbintvec = (ipsflag & SIBA_IPS_INT3_MASK) >>
SIBA_IPS_INT3_SHIFT;
break;
case 4:
sbintvec = (ipsflag & SIBA_IPS_INT4_MASK) >>
SIBA_IPS_INT4_SHIFT;
break;
default:
panic("invalid irq %u", mips_irq);
}
}
/* Ignore interrupts not routed to this MIPS IRQ */
sbstatus &= sbintvec;
/* Handle isrc_solo direct dispatch path */
isrc_solo = cpuirq->isrc_solo;
if (isrc_solo != NULL) {
if (sbstatus & BCM_MIPS_IVEC_MASK(isrc_solo)) {
error = intr_isrc_dispatch(&isrc_solo->isrc,
curthread->td_intr_frame);
if (error) {
device_printf(sc->dev, "Stray interrupt %u "
"detected\n", isrc_solo->ivec);
bcm_bmips_pic_disable_intr(sc->dev,
&isrc_solo->isrc);
}
}
sbstatus &= ~(BCM_MIPS_IVEC_MASK(isrc_solo));
if (sbstatus == 0)
return (FILTER_HANDLED);
/* Report and mask additional stray interrupts */
while ((i = fls(sbstatus)) != 0) {
i--; /* Get a 0-offset interrupt. */
sbstatus &= ~(1 << i);
device_printf(sc->dev, "Stray interrupt %u "
"detected\n", i);
bcm_bmips_mask_irq(sc, mips_irq, i);
}
return (FILTER_HANDLED);
}
/* Standard dispatch path */
while ((i = fls(sbstatus)) != 0) {
i--; /* Get a 0-offset interrupt. */
sbstatus &= ~(1 << i);
KASSERT(i < nitems(sc->bcm_mips.isrcs), ("invalid ivec %u", i));
error = intr_isrc_dispatch(&sc->bcm_mips.isrcs[i].isrc,
curthread->td_intr_frame);
if (error) {
device_printf(sc->dev, "Stray interrupt %u detected\n",
i);
bcm_bmips_mask_irq(sc, mips_irq, i);
continue;
}
}
return (FILTER_HANDLED);
}
static device_method_t bcm_bmips_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, bcm_bmips_probe),
DEVMETHOD(device_attach, bcm_bmips_attach),
DEVMETHOD(device_detach, bcm_bmips_detach),
/* Interrupt controller interface */
DEVMETHOD(pic_disable_intr, bcm_bmips_pic_disable_intr),
DEVMETHOD(pic_enable_intr, bcm_bmips_pic_enable_intr),
DEVMETHOD(pic_pre_ithread, bcm_bmips_pic_pre_ithread),
DEVMETHOD(pic_post_ithread, bcm_bmips_pic_post_ithread),
DEVMETHOD(pic_post_filter, bcm_bmips_pic_post_filter),
DEVMETHOD_END
};
static devclass_t bcm_mips_devclass;
DEFINE_CLASS_0(bcm_mips, bcm_bmips_driver, bcm_bmips_methods, sizeof(struct bcm_bmips_softc));
EARLY_DRIVER_MODULE(bcm_bmips, bhnd, bcm_bmips_driver, bcm_mips_devclass, 0, 0, BUS_PASS_CPU + BUS_PASS_ORDER_EARLY);
DEFINE_CLASS_1(bcm_mips, bcm_bmips_driver, bcm_bmips_methods, sizeof(struct bcm_bmips_softc), bcm_mips_driver);
EARLY_DRIVER_MODULE(bcm_bmips, bhnd, bcm_bmips_driver, bcm_mips_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE);
MODULE_VERSION(bcm_bmips, 1);
MODULE_DEPEND(bcm_bmips, bhnd, 1, 1, 1);

View File

@ -126,6 +126,13 @@ static const struct bhnd_core_match bcm_chipc_cores[] = {
{ BHND_MATCH_CORE(BHND_MFGID_BCM, BHND_COREID_4706_CC) },
};
static const struct bhnd_core_match bcm_cpu0_cores[] = {
{
BHND_MATCH_CORE_CLASS(BHND_DEVCLASS_CPU),
BHND_MATCH_CORE_UNIT(0)
}
};
static const struct bhnd_core_match bcm_pmu_cores[] = {
{ BHND_MATCH_CORE(BHND_MFGID_BCM, BHND_COREID_PMU) },
};
@ -429,6 +436,14 @@ bcm_init_platform_data(struct bcm_platform *bp)
}
}
/* Find CPU core info */
error = bcm_find_core(bp, bcm_cpu0_cores, nitems(bcm_cpu0_cores),
&bp->cpu_id, &bp->cpu_addr);
if (error) {
BCM_ERR("error locating CPU core: %d\n", error);
return (error);
}
/* Initialize our platform service registry */
if ((error = bhnd_service_registry_init(&bp->services))) {
BCM_ERR("error initializing service registry: %d\n", error);

View File

@ -55,6 +55,9 @@ struct bcm_platform {
uint32_t cc_caps; /**< chipc capabilities */
uint32_t cc_caps_ext; /**< chipc extended capabilies */
struct bhnd_core_info cpu_id; /**< cpu core info */
uintptr_t cpu_addr; /**< cpu core phys address */
/* On non-AOB devices, the PMU register block is mapped to chipc;
* the pmu_id and pmu_addr values will be copied from cc_id
* and cc_addr. */
@ -122,6 +125,11 @@ int bcm_get_nvram(struct bcm_platform *bp,
#define BCM_CHIPC_WRITE_4(_bp, _reg, _val) \
BCM_CORE_WRITE_4(_bp, cc_addr, (_reg), (_val))
#define BCM_CPU_READ_4(_bp, _reg) \
BCM_CORE_READ_4(_bp, cpu_addr, (_reg))
#define BCM_CPU_WRITE_4(_bp, _reg, _val) \
BCM_CORE_WRITE_4(_bp, cpu_addr, (_reg), (_val))
#define BCM_PMU_READ_4(_bp, _reg) \
BCM_CORE_READ_4(_bp, pmu_addr, (_reg))
#define BCM_PMU_WRITE_4(_bp, _reg, _val) \

View File

@ -0,0 +1,698 @@
/*-
* Copyright (c) 2017 The FreeBSD Foundation
* All rights reserved.
*
* This software was developed by Landon Fuller under sponsorship from
* the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/bus.h>
#include <sys/module.h>
#include <sys/limits.h>
#include <sys/systm.h>
#include <machine/bus.h>
#include <machine/intr.h>
#include <machine/resource.h>
#include <sys/rman.h>
#include <dev/bhnd/bhnd.h>
#include <dev/bhnd/siba/sibareg.h>
#include "pic_if.h"
#include "bcm_mipsvar.h"
/*
* Broadcom MIPS core driver.
*
* Abstract driver for Broadcom MIPS CPU/PIC cores.
*/
static uintptr_t bcm_mips_pic_xref(struct bcm_mips_softc *sc);
static device_t bcm_mips_find_bhnd_parent(device_t dev);
static int bcm_mips_retain_cpu_intr(struct bcm_mips_softc *sc,
struct bcm_mips_irqsrc *isrc, struct resource *res);
static int bcm_mips_release_cpu_intr(struct bcm_mips_softc *sc,
struct bcm_mips_irqsrc *isrc, struct resource *res);
static const int bcm_mips_debug = 0;
#define DPRINTF(fmt, ...) do { \
if (bcm_mips_debug) \
printf("%s: " fmt, __FUNCTION__, ##__VA_ARGS__); \
} while (0)
#define DENTRY(dev, fmt, ...) do { \
if (bcm_mips_debug) \
printf("%s(%s, " fmt ")\n", __FUNCTION__, \
device_get_nameunit(dev), ##__VA_ARGS__); \
} while (0)
/**
* Register all interrupt source definitions.
*/
static int
bcm_mips_register_isrcs(struct bcm_mips_softc *sc)
{
const char *name;
uintptr_t xref;
int error;
xref = bcm_mips_pic_xref(sc);
name = device_get_nameunit(sc->dev);
for (size_t ivec = 0; ivec < nitems(sc->isrcs); ivec++) {
sc->isrcs[ivec].ivec = ivec;
sc->isrcs[ivec].cpuirq = NULL;
sc->isrcs[ivec].refs = 0;
error = intr_isrc_register(&sc->isrcs[ivec].isrc, sc->dev,
xref, "%s,%u", name, ivec);
if (error) {
for (size_t i = 0; i < ivec; i++)
intr_isrc_deregister(&sc->isrcs[i].isrc);
device_printf(sc->dev, "error registering IRQ %zu: "
"%d\n", ivec, error);
return (error);
}
}
return (0);
}
/**
* Initialize the given @p cpuirq state as unavailable.
*
* @param sc BHND MIPS driver instance state.
* @param cpuirq The CPU IRQ state to be initialized.
*
* @retval 0 success
* @retval non-zero if initializing @p cpuirq otherwise fails, a regular
* unix error code will be returned.
*/
static int
bcm_mips_init_cpuirq_unavail(struct bcm_mips_softc *sc,
struct bcm_mips_cpuirq *cpuirq)
{
BCM_MIPS_LOCK(sc);
KASSERT(cpuirq->sc == NULL, ("cpuirq already initialized"));
cpuirq->sc = sc;
cpuirq->mips_irq = 0;
cpuirq->irq_rid = -1;
cpuirq->irq_res = NULL;
cpuirq->irq_cookie = NULL;
cpuirq->isrc_solo = NULL;
cpuirq->refs = 0;
BCM_MIPS_UNLOCK(sc);
return (0);
}
/**
* Allocate required resources and initialize the given @p cpuirq state.
*
* @param sc BHND MIPS driver instance state.
* @param cpuirq The CPU IRQ state to be initialized.
* @param rid The resource ID to be assigned for the CPU IRQ resource,
* or -1 if no resource should be assigned.
* @param irq The MIPS HW IRQ# to be allocated.
* @param filter The interrupt filter to be setup.
*
* @retval 0 success
* @retval non-zero if initializing @p cpuirq otherwise fails, a regular
* unix error code will be returned.
*/
static int
bcm_mips_init_cpuirq(struct bcm_mips_softc *sc, struct bcm_mips_cpuirq *cpuirq,
int rid, u_int irq, driver_filter_t filter)
{
struct resource *res;
void *cookie;
u_int irq_real;
int error;
/* Must fall within MIPS HW IRQ range */
if (irq >= NHARD_IRQS)
return (EINVAL);
/* HW IRQs are numbered relative to SW IRQs */
irq_real = irq + NSOFT_IRQS;
/* Try to assign and allocate the resource */
BCM_MIPS_LOCK(sc);
KASSERT(cpuirq->sc == NULL, ("cpuirq already initialized"));
error = bus_set_resource(sc->dev, SYS_RES_IRQ, rid, irq_real, 1);
if (error) {
BCM_MIPS_UNLOCK(sc);
device_printf(sc->dev, "failed to assign interrupt %u: "
"%d\n", irq, error);
return (error);
}
res = bus_alloc_resource_any(sc->dev, SYS_RES_IRQ, &rid, RF_ACTIVE);
if (res == NULL) {
BCM_MIPS_UNLOCK(sc);
device_printf(sc->dev, "failed to allocate interrupt "
"%u resource\n", irq);
bus_delete_resource(sc->dev, SYS_RES_IRQ, rid);
return (ENXIO);
}
error = bus_setup_intr(sc->dev, res,
INTR_TYPE_MISC | INTR_MPSAFE | INTR_EXCL, filter, NULL, cpuirq,
&cookie);
if (error) {
BCM_MIPS_UNLOCK(sc);
printf("failed to setup internal interrupt handler: %d\n",
error);
bus_release_resource(sc->dev, SYS_RES_IRQ, rid, res);
bus_delete_resource(sc->dev, SYS_RES_IRQ, rid);
return (error);
}
/* Initialize CPU IRQ state */
cpuirq->sc = sc;
cpuirq->mips_irq = irq;
cpuirq->irq_rid = rid;
cpuirq->irq_res = res;
cpuirq->irq_cookie = cookie;
cpuirq->isrc_solo = NULL;
cpuirq->refs = 0;
BCM_MIPS_UNLOCK(sc);
return (0);
}
/**
* Free any resources associated with the given @p cpuirq state.
*
* @param sc BHND MIPS driver instance state.
* @param cpuirq A CPU IRQ instance previously successfully initialized
* via bcm_mips_init_cpuirq().
*
* @retval 0 success
* @retval non-zero if finalizing @p cpuirq otherwise fails, a regular
* unix error code will be returned.
*/
static int
bcm_mips_fini_cpuirq(struct bcm_mips_softc *sc, struct bcm_mips_cpuirq *cpuirq)
{
int error;
BCM_MIPS_LOCK(sc);
if (cpuirq->sc == NULL) {
KASSERT(cpuirq->irq_res == NULL, ("leaking cpuirq resource"));
BCM_MIPS_UNLOCK(sc);
return (0); /* not initialized */
}
if (cpuirq->refs != 0) {
BCM_MIPS_UNLOCK(sc);
return (EBUSY);
}
if (cpuirq->irq_cookie != NULL) {
KASSERT(cpuirq->irq_res != NULL, ("resource missing"));
error = bus_teardown_intr(sc->dev, cpuirq->irq_res,
cpuirq->irq_cookie);
if (error) {
BCM_MIPS_UNLOCK(sc);
return (error);
}
cpuirq->irq_cookie = NULL;
}
if (cpuirq->irq_res != NULL) {
bus_release_resource(sc->dev, SYS_RES_IRQ, cpuirq->irq_rid,
cpuirq->irq_res);
cpuirq->irq_res = NULL;
}
if (cpuirq->irq_rid != -1) {
bus_delete_resource(sc->dev, SYS_RES_IRQ, cpuirq->irq_rid);
cpuirq->irq_rid = -1;
}
BCM_MIPS_UNLOCK(sc);
return (0);
}
static int
bcm_mips_attach_default(device_t dev)
{
/* subclassing drivers must provide an implementation of
* DEVICE_ATTACH() */
panic("device_attach() unimplemented");
}
/**
* BHND MIPS device attach.
*
* This must be called from subclass drivers' DEVICE_ATTACH().
*
* @param dev BHND MIPS device.
* @param num_cpuirqs The number of usable MIPS HW IRQs.
* @param timer_irq The MIPS HW IRQ assigned to the MIPS CPU timer.
* @param filter The subclass's core-specific IRQ dispatch filter. Will be
* passed the associated bcm_mips_cpuirq instance as its argument.
*/
int
bcm_mips_attach(device_t dev, u_int num_cpuirqs, u_int timer_irq,
driver_filter_t filter)
{
struct bcm_mips_softc *sc;
struct intr_pic *pic;
uintptr_t xref;
u_int irq_rid;
rman_res_t irq;
int error;
sc = device_get_softc(dev);
sc->dev = dev;
sc->num_cpuirqs = num_cpuirqs;
sc->timer_irq = timer_irq;
/* Must not exceed the actual size of our fixed IRQ array */
if (sc->num_cpuirqs > nitems(sc->cpuirqs)) {
device_printf(dev, "%u nirqs exceeds maximum supported %zu",
sc->num_cpuirqs, nitems(sc->cpuirqs));
return (ENXIO);
}
pic = NULL;
xref = bcm_mips_pic_xref(sc);
BCM_MIPS_LOCK_INIT(sc);
/* Register our interrupt sources */
if ((error = bcm_mips_register_isrcs(sc))) {
BCM_MIPS_LOCK_DESTROY(sc);
return (error);
}
/* Initialize our CPU interrupt state */
irq_rid = bhnd_get_intr_count(dev); /* last bhnd-assigned RID + 1 */
irq = 0;
for (u_int i = 0; i < sc->num_cpuirqs; i++) {
/* Must not overflow signed resource ID representation */
if (irq_rid >= INT_MAX) {
device_printf(dev, "exhausted IRQ resource IDs\n");
error = ENOMEM;
goto failed;
}
if (irq == sc->timer_irq) {
/* Mark the CPU timer's IRQ as unavailable */
error = bcm_mips_init_cpuirq_unavail(sc,
&sc->cpuirqs[i]);
} else {
/* Initialize state */
error = bcm_mips_init_cpuirq(sc, &sc->cpuirqs[i],
irq_rid, irq, filter);
}
if (error)
goto failed;
/* Increment IRQ and resource ID for next allocation */
irq_rid++;
irq++;
}
/* Sanity check; our shared IRQ must be available */
if (sc->num_cpuirqs <= BCM_MIPS_IRQ_SHARED)
panic("missing shared interrupt %d\n", BCM_MIPS_IRQ_SHARED);
if (sc->cpuirqs[BCM_MIPS_IRQ_SHARED].irq_rid == -1)
panic("shared interrupt %d unavailable", BCM_MIPS_IRQ_SHARED);
/* Register PIC */
if ((pic = intr_pic_register(dev, xref)) == NULL) {
device_printf(dev, "error registering PIC\n");
error = ENXIO;
goto failed;
}
return (0);
failed:
/* Deregister PIC before performing any other cleanup */
if (pic != NULL)
intr_pic_deregister(dev, 0);
/* Deregister all interrupt sources */
for (size_t i = 0; i < nitems(sc->isrcs); i++)
intr_isrc_deregister(&sc->isrcs[i].isrc);
/* Free our MIPS CPU interrupt handler state */
for (u_int i = 0; i < sc->num_cpuirqs; i++)
bcm_mips_fini_cpuirq(sc, &sc->cpuirqs[i]);
BCM_MIPS_LOCK_DESTROY(sc);
return (error);
}
int
bcm_mips_detach(device_t dev)
{
struct bcm_mips_softc *sc;
sc = device_get_softc(dev);
/* Deregister PIC before performing any other cleanup */
intr_pic_deregister(dev, 0);
/* Deregister all interrupt sources */
for (size_t i = 0; i < nitems(sc->isrcs); i++)
intr_isrc_deregister(&sc->isrcs[i].isrc);
/* Free our MIPS CPU interrupt handler state */
for (u_int i = 0; i < sc->num_cpuirqs; i++)
bcm_mips_fini_cpuirq(sc, &sc->cpuirqs[i]);
return (0);
}
/* PIC_MAP_INTR() */
static int
bcm_mips_pic_map_intr(device_t dev, struct intr_map_data *d,
struct intr_irqsrc **isrcp)
{
struct bcm_mips_softc *sc;
struct bcm_mips_intr_map_data *data;
sc = device_get_softc(dev);
if (d->type != INTR_MAP_DATA_BCM_MIPS) {
DENTRY(dev, "type=%d", d->type);
return (ENOTSUP);
}
data = (struct bcm_mips_intr_map_data *)d;
DENTRY(dev, "type=%d, ivec=%u", d->type, data->ivec);
if (data->ivec < 0 || data->ivec >= nitems(sc->isrcs))
return (EINVAL);
*isrcp = &sc->isrcs[data->ivec].isrc;
return (0);
}
/* PIC_SETUP_INTR() */
static int
bcm_mips_pic_setup_intr(device_t dev, struct intr_irqsrc *irqsrc,
struct resource *res, struct intr_map_data *data)
{
struct bcm_mips_softc *sc;
struct bcm_mips_irqsrc *isrc;
int error;
sc = device_get_softc(dev);
isrc = (struct bcm_mips_irqsrc *)irqsrc;
/* Assign a CPU interrupt */
BCM_MIPS_LOCK(sc);
error = bcm_mips_retain_cpu_intr(sc, isrc, res);
BCM_MIPS_UNLOCK(sc);
return (error);
}
/* PIC_TEARDOWN_INTR() */
static int
bcm_mips_pic_teardown_intr(device_t dev, struct intr_irqsrc *irqsrc,
struct resource *res, struct intr_map_data *data)
{
struct bcm_mips_softc *sc;
struct bcm_mips_irqsrc *isrc;
int error;
sc = device_get_softc(dev);
isrc = (struct bcm_mips_irqsrc *)irqsrc;
/* Release the CPU interrupt */
BCM_MIPS_LOCK(sc);
error = bcm_mips_release_cpu_intr(sc, isrc, res);
BCM_MIPS_UNLOCK(sc);
return (error);
}
/** return our PIC's xref */
static uintptr_t
bcm_mips_pic_xref(struct bcm_mips_softc *sc)
{
uintptr_t xref;
/* Determine our interrupt domain */
xref = BHND_BUS_GET_INTR_DOMAIN(device_get_parent(sc->dev), sc->dev,
true);
KASSERT(xref != 0, ("missing interrupt domain"));
return (xref);
}
/**
* Walk up the device tree from @p dev until we find a bhnd-attached core,
* returning either the core, or NULL if @p dev is not attached under a bhnd
* bus.
*/
static device_t
bcm_mips_find_bhnd_parent(device_t dev)
{
device_t core, bus;
devclass_t bhnd_class;
bhnd_class = devclass_find("bhnd");
core = dev;
while ((bus = device_get_parent(core)) != NULL) {
if (device_get_devclass(bus) == bhnd_class)
return (core);
core = bus;
}
/* Not found */
return (NULL);
}
/**
* Retain @p isrc and assign a MIPS CPU interrupt on behalf of @p res; if
* the @p isrc already has a MIPS CPU interrupt assigned, the existing
* reference will be left unmodified.
*
* @param sc BHND MIPS driver state.
* @param isrc The interrupt source corresponding to @p res.
* @param res The interrupt resource for which a MIPS CPU IRQ will be
* assigned.
*/
static int
bcm_mips_retain_cpu_intr(struct bcm_mips_softc *sc,
struct bcm_mips_irqsrc *isrc, struct resource *res)
{
struct bcm_mips_cpuirq *cpuirq;
bhnd_devclass_t devclass;
device_t core;
BCM_MIPS_LOCK_ASSERT(sc, MA_OWNED);
/* Prefer existing assignment */
if (isrc->cpuirq != NULL) {
KASSERT(isrc->cpuirq->refs > 0, ("assigned IRQ has no "
"references"));
/* Increment our reference count */
if (isrc->refs == UINT_MAX)
return (ENOMEM); /* would overflow */
isrc->refs++;
return (0);
}
/* Use the device class of the bhnd core to which the interrupt
* vector is routed to determine whether a shared interrupt should
* be preferred. */
devclass = BHND_DEVCLASS_OTHER;
core = bcm_mips_find_bhnd_parent(rman_get_device(res));
if (core != NULL)
devclass = bhnd_get_class(core);
switch (devclass) {
case BHND_DEVCLASS_CC:
case BHND_DEVCLASS_CC_B:
case BHND_DEVCLASS_PMU:
case BHND_DEVCLASS_RAM:
case BHND_DEVCLASS_MEMC:
case BHND_DEVCLASS_CPU:
case BHND_DEVCLASS_SOC_ROUTER:
case BHND_DEVCLASS_SOC_BRIDGE:
case BHND_DEVCLASS_EROM:
case BHND_DEVCLASS_NVRAM:
/* Always use a shared interrupt for these devices */
cpuirq = &sc->cpuirqs[BCM_MIPS_IRQ_SHARED];
break;
case BHND_DEVCLASS_PCI:
case BHND_DEVCLASS_PCIE:
case BHND_DEVCLASS_PCCARD:
case BHND_DEVCLASS_ENET:
case BHND_DEVCLASS_ENET_MAC:
case BHND_DEVCLASS_ENET_PHY:
case BHND_DEVCLASS_WLAN:
case BHND_DEVCLASS_WLAN_MAC:
case BHND_DEVCLASS_WLAN_PHY:
case BHND_DEVCLASS_USB_HOST:
case BHND_DEVCLASS_USB_DEV:
case BHND_DEVCLASS_USB_DUAL:
case BHND_DEVCLASS_OTHER:
case BHND_DEVCLASS_INVALID:
default:
/* Fall back on a shared interrupt */
cpuirq = &sc->cpuirqs[BCM_MIPS_IRQ_SHARED];
/* Try to assign a dedicated MIPS HW interrupt */
for (u_int i = 0; i < sc->num_cpuirqs; i++) {
if (i == BCM_MIPS_IRQ_SHARED)
continue;
if (sc->cpuirqs[i].irq_rid == -1)
continue; /* unavailable */
if (sc->cpuirqs[i].refs != 0)
continue; /* already assigned */
/* Found an unused CPU IRQ */
cpuirq = &sc->cpuirqs[i];
break;
}
break;
}
DPRINTF("routing backplane interrupt vector %u to MIPS IRQ %u\n",
isrc->ivec, cpuirq->mips_irq);
KASSERT(isrc->cpuirq == NULL, ("CPU IRQ already assigned"));
KASSERT(isrc->refs == 0, ("isrc has active references with no "
"assigned CPU IRQ"));
KASSERT(cpuirq->refs == 1 || cpuirq->isrc_solo == NULL,
("single isrc dispatch enabled on cpuirq with multiple refs"));
/* Verify that bumping the cpuirq refcount below will not overflow */
if (cpuirq->refs == UINT_MAX)
return (ENOMEM);
/* Increment cpuirq refcount on behalf of the isrc */
cpuirq->refs++;
/* Increment isrc refcount on behalf of the caller */
isrc->refs++;
/* Assign the IRQ to the isrc */
isrc->cpuirq = cpuirq;
/* Can we enable the single isrc dispatch path? */
if (cpuirq->refs == 1 && cpuirq->mips_irq != BCM_MIPS_IRQ_SHARED)
cpuirq->isrc_solo = isrc;
return (0);
}
/**
* Release the MIPS CPU interrupt assigned to @p isrc on behalf of @p res.
*
* @param sc BHND MIPS driver state.
* @param isrc The interrupt source corresponding to @p res.
* @param res The interrupt resource being activated.
*/
static int
bcm_mips_release_cpu_intr(struct bcm_mips_softc *sc,
struct bcm_mips_irqsrc *isrc, struct resource *res)
{
struct bcm_mips_cpuirq *cpuirq;
BCM_MIPS_LOCK_ASSERT(sc, MA_OWNED);
/* Decrement the refcount */
KASSERT(isrc->refs > 0, ("isrc over-release"));
isrc->refs--;
/* Nothing else to do if the isrc is still actively referenced */
if (isrc->refs > 0)
return (0);
/* Otherwise, we need to release our CPU IRQ reference */
cpuirq = isrc->cpuirq;
isrc->cpuirq = NULL;
KASSERT(cpuirq != NULL, ("no assigned IRQ"));
KASSERT(cpuirq->refs > 0, ("cpuirq over-release"));
/* Disable single isrc dispatch path */
if (cpuirq->refs == 1 && cpuirq->isrc_solo != NULL) {
KASSERT(cpuirq->isrc_solo == isrc, ("invalid solo isrc"));
cpuirq->isrc_solo = NULL;
}
cpuirq->refs--;
return (0);
}
static device_method_t bcm_mips_methods[] = {
/* Device interface */
DEVMETHOD(device_attach, bcm_mips_attach_default),
DEVMETHOD(device_detach, bcm_mips_detach),
/* Interrupt controller interface */
DEVMETHOD(pic_map_intr, bcm_mips_pic_map_intr),
DEVMETHOD(pic_setup_intr, bcm_mips_pic_setup_intr),
DEVMETHOD(pic_teardown_intr, bcm_mips_pic_teardown_intr),
DEVMETHOD_END
};
DEFINE_CLASS_0(bcm_mips, bcm_mips_driver, bcm_mips_methods, sizeof(struct bcm_mips_softc));
MODULE_VERSION(bcm_mips, 1);
MODULE_DEPEND(bcm_mips, bhnd, 1, 1, 1);

View File

@ -1,8 +1,12 @@
/*-
* Copyright (c) 2016 Michael Zhilin <mizhka@gmail.com>
* Copyright (c) 2016 Landon Fuller <landonf@FreeBSD.org>
* Copyright (c) 2017 The FreeBSD Foundation
* All rights reserved.
*
* Portions of this software were developed by Landon Fuller
* under sponsorship from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
@ -35,43 +39,106 @@ __FBSDID("$FreeBSD$");
#include <sys/kernel.h>
#include <sys/bus.h>
#include <sys/module.h>
#include <sys/proc.h>
#include <machine/bus.h>
#include <sys/rman.h>
#include <machine/cpufunc.h>
#include <machine/intr.h>
#include <machine/resource.h>
#include <dev/bhnd/bhnd.h>
#include <dev/bhnd/bcma/bcma_dmp.h>
#include "pic_if.h"
#include "bcm_machdep.h"
#include "bcm_mipsvar.h"
#include "bcm_mips74kreg.h"
/*
* Broadcom MIPS74K Core
*
* These cores are only found on bcma(4) chipsets, allowing
* us to assume the availability of bcma interrupt registers.
* These cores are only found on bcma(4) chipsets.
*/
struct bcm_mips74k_softc;
static int bcm_mips74k_pic_intr(void *arg);
static void bcm_mips74k_mask_irq(struct bcm_mips74k_softc *sc,
u_int mips_irq, u_int ivec);
static void bcm_mips74k_unmask_irq(struct bcm_mips74k_softc *sc,
u_int mips_irq, u_int ivec);
static const struct bhnd_device bcm_mips74k_devs[] = {
BHND_DEVICE(MIPS, MIPS74K, NULL, NULL, BHND_DF_SOC),
BHND_DEVICE_END
};
struct bcm_mips74k_softc {
struct bcm_mips_softc bcm_mips; /**< parent softc */
device_t dev;
struct resource *mem_res;
struct resource *mem; /**< cpu core registers */
int mem_rid;
};
/* Early routing of the CPU timer interrupt is required */
static void
bcm_mips74k_timer_init(void *unused)
{
struct bcm_platform *bp;
u_int irq;
uint32_t mask;
bp = bcm_get_platform();
/* Must be a MIPS74K core attached to a BCMA interconnect */
if (!bhnd_core_matches(&bp->cpu_id, &(struct bhnd_core_match) {
BHND_MATCH_CORE(BHND_MFGID_MIPS, BHND_COREID_MIPS74K)
})) {
if (bootverbose) {
BCM_ERR("not a MIPS74K core: %s %s\n",
bhnd_vendor_name(bp->cpu_id.vendor),
bhnd_core_name(&bp->cpu_id));
}
return;
}
if (!BHND_CHIPTYPE_IS_BCMA_COMPATIBLE(bp->cid.chip_type)) {
if (bootverbose)
BCM_ERR("not a BCMA device\n");
return;
}
/* Route the timer bus ivec to the CPU's timer IRQ, and disable any
* other vectors assigned to the IRQ. */
irq = BCM_MIPS74K_GET_TIMER_IRQ();
mask = BCM_MIPS74K_INTR_SEL_FLAG(BCM_MIPS74K_TIMER_IVEC);
BCM_CPU_WRITE_4(bp, BCM_MIPS74K_INTR_SEL(irq), mask);
}
static int
bcm_mips74k_probe(device_t dev)
{
const struct bhnd_device *id;
const struct bhnd_chipid *cid;
id = bhnd_device_lookup(dev, bcm_mips74k_devs,
sizeof(bcm_mips74k_devs[0]));
if (id == NULL)
return (ENXIO);
/* Check the chip type; the MIPS74K core should only be found
* on bcma(4) chipsets (and we rely on bcma OOB interrupt
* routing). */
cid = bhnd_get_chipid(dev);
if (!BHND_CHIPTYPE_IS_BCMA_COMPATIBLE(cid->chip_type))
return (ENXIO);
bhnd_set_default_core_desc(dev);
return (BUS_PROBE_DEFAULT);
}
@ -80,20 +147,39 @@ static int
bcm_mips74k_attach(device_t dev)
{
struct bcm_mips74k_softc *sc;
u_int timer_irq;
int error;
sc = device_get_softc(dev);
sc->dev = dev;
/* Allocate bus resources */
/* Allocate our core's register block */
sc->mem_rid = 0;
sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->mem_rid,
sc->mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->mem_rid,
RF_ACTIVE);
if (sc->mem_res == NULL)
if (sc->mem == NULL) {
device_printf(dev, "failed to allocate cpu register block\n");
return (ENXIO);
}
/* Route MIPS timer to IRQ5 */
bus_write_4(sc->mem_res, BCM_MIPS74K_INTR5_SEL,
(1<<BCM_MIPS74K_TIMER_IVEC));
/* Clear interrupt map */
timer_irq = BCM_MIPS74K_GET_TIMER_IRQ();
for (size_t i = 0; i < BCM_MIPS74K_NUM_INTR; i++) {
/* We don't use the timer IRQ; leave it routed to the
* MIPS CPU */
if (i == timer_irq)
continue;
bus_write_4(sc->mem, BCM_MIPS74K_INTR_SEL(i), 0);
}
/* Initialize the generic BHND MIPS driver state */
error = bcm_mips_attach(dev, BCM_MIPS74K_NUM_INTR, timer_irq,
bcm_mips74k_pic_intr);
if (error) {
bus_release_resource(dev, SYS_RES_MEMORY, sc->mem_rid, sc->mem);
return (error);
}
return (0);
}
@ -102,26 +188,205 @@ static int
bcm_mips74k_detach(device_t dev)
{
struct bcm_mips74k_softc *sc;
int error;
sc = device_get_softc(dev);
bus_release_resource(dev, SYS_RES_MEMORY, sc->mem_rid, sc->mem_res);
if ((error = bcm_mips_detach(dev)))
return (error);
bus_release_resource(dev, SYS_RES_MEMORY, sc->mem_rid, sc->mem);
return (0);
}
/* PIC_DISABLE_INTR() */
static void
bcm_mips74k_pic_disable_intr(device_t dev, struct intr_irqsrc *irqsrc)
{
struct bcm_mips74k_softc *sc;
struct bcm_mips_irqsrc *isrc;
sc = device_get_softc(dev);
isrc = (struct bcm_mips_irqsrc *)irqsrc;
KASSERT(isrc->cpuirq != NULL, ("no assigned MIPS IRQ"));
bcm_mips74k_mask_irq(sc, isrc->cpuirq->mips_irq, isrc->ivec);
}
/* PIC_ENABLE_INTR() */
static void
bcm_mips74k_pic_enable_intr(device_t dev, struct intr_irqsrc *irqsrc)
{
struct bcm_mips74k_softc *sc;
struct bcm_mips_irqsrc *isrc;
sc = device_get_softc(dev);
isrc = (struct bcm_mips_irqsrc *)irqsrc;
KASSERT(isrc->cpuirq != NULL, ("no assigned MIPS IRQ"));
bcm_mips74k_unmask_irq(sc, isrc->cpuirq->mips_irq, isrc->ivec);
}
/* PIC_PRE_ITHREAD() */
static void
bcm_mips74k_pic_pre_ithread(device_t dev, struct intr_irqsrc *isrc)
{
bcm_mips74k_pic_disable_intr(dev, isrc);
}
/* PIC_POST_ITHREAD() */
static void
bcm_mips74k_pic_post_ithread(device_t dev, struct intr_irqsrc *isrc)
{
bcm_mips74k_pic_enable_intr(dev, isrc);
}
/* PIC_POST_FILTER() */
static void
bcm_mips74k_pic_post_filter(device_t dev, struct intr_irqsrc *isrc)
{
}
/**
* Disable routing of backplane interrupt vector @p ivec to MIPS IRQ
* @p mips_irq.
*/
static void
bcm_mips74k_mask_irq(struct bcm_mips74k_softc *sc, u_int mips_irq, u_int ivec)
{
uint32_t oobsel;
KASSERT(mips_irq < sc->bcm_mips.num_cpuirqs, ("invalid MIPS IRQ %u",
mips_irq));
KASSERT(mips_irq < BCM_MIPS74K_NUM_INTR, ("unsupported MIPS IRQ %u",
mips_irq));
KASSERT(ivec < BCMA_OOB_NUM_BUSLINES, ("invalid backplane ivec"));
oobsel = bus_read_4(sc->mem, BCM_MIPS74K_INTR_SEL(mips_irq));
oobsel &= ~(BCM_MIPS74K_INTR_SEL_FLAG(ivec));
bus_write_4(sc->mem, BCM_MIPS74K_INTR_SEL(mips_irq), oobsel);
}
/**
* Enable routing of an interrupt.
*/
static void
bcm_mips74k_unmask_irq(struct bcm_mips74k_softc *sc, u_int mips_irq, u_int ivec)
{
uint32_t oobsel;
KASSERT(mips_irq < sc->bcm_mips.num_cpuirqs, ("invalid MIPS IRQ %u",
mips_irq));
KASSERT(mips_irq < BCM_MIPS74K_NUM_INTR, ("unsupported MIPS IRQ %u",
mips_irq));
KASSERT(ivec < BCMA_OOB_NUM_BUSLINES, ("invalid backplane ivec"));
oobsel = bus_read_4(sc->mem, BCM_MIPS74K_INTR_SEL(mips_irq));
oobsel |= BCM_MIPS74K_INTR_SEL_FLAG(ivec);
bus_write_4(sc->mem, BCM_MIPS74K_INTR_SEL(mips_irq), oobsel);
}
/* our MIPS CPU interrupt filter */
static int
bcm_mips74k_pic_intr(void *arg)
{
struct bcm_mips74k_softc *sc;
struct bcm_mips_cpuirq *cpuirq;
struct bcm_mips_irqsrc *isrc_solo;
uint32_t oobsel, intr;
u_int i;
int error;
cpuirq = arg;
sc = (struct bcm_mips74k_softc*)cpuirq->sc;
/* Fetch current interrupt state */
intr = bus_read_4(sc->mem, BCM_MIPS74K_INTR_STATUS);
/* Fetch mask of interrupt vectors routed to this MIPS IRQ */
KASSERT(cpuirq->mips_irq < BCM_MIPS74K_NUM_INTR,
("invalid irq %u", cpuirq->mips_irq));
oobsel = bus_read_4(sc->mem, BCM_MIPS74K_INTR_SEL(cpuirq->mips_irq));
/* Ignore interrupts not routed to this MIPS IRQ */
intr &= oobsel;
/* Handle isrc_solo direct dispatch path */
isrc_solo = cpuirq->isrc_solo;
if (isrc_solo != NULL) {
if (intr & BCM_MIPS_IVEC_MASK(isrc_solo)) {
error = intr_isrc_dispatch(&isrc_solo->isrc,
curthread->td_intr_frame);
if (error) {
device_printf(sc->dev, "Stray interrupt %u "
"detected\n", isrc_solo->ivec);
bcm_mips74k_pic_disable_intr(sc->dev,
&isrc_solo->isrc);
}
}
intr &= ~(BCM_MIPS_IVEC_MASK(isrc_solo));
if (intr == 0)
return (FILTER_HANDLED);
/* Report and mask additional stray interrupts */
while ((i = fls(intr)) != 0) {
i--; /* Get a 0-offset interrupt. */
intr &= ~(1 << i);
device_printf(sc->dev, "Stray interrupt %u "
"detected\n", i);
bcm_mips74k_mask_irq(sc, cpuirq->mips_irq, i);
}
return (FILTER_HANDLED);
}
/* Standard dispatch path */
while ((i = fls(intr)) != 0) {
i--; /* Get a 0-offset interrupt. */
intr &= ~(1 << i);
KASSERT(i < nitems(sc->bcm_mips.isrcs), ("invalid ivec %u", i));
error = intr_isrc_dispatch(&sc->bcm_mips.isrcs[i].isrc,
curthread->td_intr_frame);
if (error) {
device_printf(sc->dev, "Stray interrupt %u detected\n",
i);
bcm_mips74k_mask_irq(sc, cpuirq->mips_irq, i);
continue;
}
}
return (FILTER_HANDLED);
}
static device_method_t bcm_mips74k_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, bcm_mips74k_probe),
DEVMETHOD(device_attach, bcm_mips74k_attach),
DEVMETHOD(device_detach, bcm_mips74k_detach),
/* Interrupt controller interface */
DEVMETHOD(pic_disable_intr, bcm_mips74k_pic_disable_intr),
DEVMETHOD(pic_enable_intr, bcm_mips74k_pic_enable_intr),
DEVMETHOD(pic_pre_ithread, bcm_mips74k_pic_pre_ithread),
DEVMETHOD(pic_post_ithread, bcm_mips74k_pic_post_ithread),
DEVMETHOD(pic_post_filter, bcm_mips74k_pic_post_filter),
DEVMETHOD_END
};
static devclass_t bcm_mips_devclass;
DEFINE_CLASS_0(bcm_mips, bcm_mips74k_driver, bcm_mips74k_methods, sizeof(struct bcm_mips74k_softc));
EARLY_DRIVER_MODULE(bcm_mips74k, bhnd, bcm_mips74k_driver, bcm_mips_devclass, 0, 0, BUS_PASS_CPU + BUS_PASS_ORDER_EARLY);
DEFINE_CLASS_1(bcm_mips, bcm_mips74k_driver, bcm_mips74k_methods, sizeof(struct bcm_mips_softc), bcm_mips_driver);
EARLY_DRIVER_MODULE(bcm_mips74k, bhnd, bcm_mips74k_driver, bcm_mips_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE);
SYSINIT(cpu_init, SI_SUB_CPU, SI_ORDER_FIRST, bcm_mips74k_timer_init, NULL);
MODULE_VERSION(bcm_mips74k, 1);
MODULE_DEPEND(bcm_mips74k, bhnd, 1, 1, 1);

View File

@ -46,9 +46,13 @@
#define BCM_MIPS74K_INTR3_SEL 0x20 /**< IRQ3 OOBSEL mask */
#define BCM_MIPS74K_INTR4_SEL 0x24 /**< IRQ4 OOBSEL mask */
#define BCM_MIPS74K_INTR5_SEL 0x28 /**< IRQ5 OOBSEL mask */
#define BCM_MIPS74K_NUM_INTR 6 /**< routable CPU interrupt count */
#define BCM_MIPS74K_INTR_SEL(_intr) \
(BCM_MIPS74K_INTR0_SEL + ((_intr) * 4))
#define BCM_MIPS74K_INTR_SEL_FLAG(_i) (1<<_i)
#define BCM_MIPS74K_TIMER_IVEC 31 /**< MIPS timer's bus interrupt vector */
#define BCM_MIPS74K_NMI_MASK 0x2C /**< nmi mask */
@ -56,7 +60,9 @@
#define BCM_MIPS74K_GPIO_OUT 0x44 /**< gpio output enable */
#define BCM_MIPS74K_GPIO_EN 0x48 /**< gpio enable */
/** The MIPS timer interrupt IRQ assignment */
#define BCM_MIPS74K_GET_TIMER_IRQ() \
((mips_rd_intctl() & MIPS_INTCTL_IPTI_MASK) >> MIPS_INTCTL_IPTI_SHIFT)
#define BCM_MIPS74K_TIMER_IVEC 31 /**< MIPS timer OOBSEL value */
#endif /* _MIPS_BROADCOM_MIPS74KREG_H_ */

View File

@ -0,0 +1,114 @@
/*-
* Copyright (c) 2017 The FreeBSD Foundation
* All rights reserved.
*
* This software was developed by Landon Fuller under sponsorship from
* the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer,
* without modification.
* 2. Redistributions in binary form must reproduce at minimum a disclaimer
* similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
* redistribution must be conditioned upon including a substantially
* similar Disclaimer requirement for further binary redistribution.
*
* NO WARRANTY
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGES.
*
* $FreeBSD$
*/
#ifndef _MIPS_BROADCOM_BCM_MIPSVAR_H_
#define _MIPS_BROADCOM_BCM_MIPSVAR_H_
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/intr.h>
#include <sys/lock.h>
#include <machine/intr.h>
DECLARE_CLASS(bcm_mips_driver);
struct bcm_mips_irqsrc;
struct bcm_mips_softc;
#define BCM_MIPS_NINTR 32 /**< maximum number of addressable backplane interrupt vectors */
#define BCM_MIPS_IRQ_SHARED 0 /**< MIPS CPU IRQ reserved for shared interrupt handling */
#define INTR_MAP_DATA_BCM_MIPS INTR_MAP_DATA_PLAT_2 /**< Broadcom MIPS PIC interrupt map data type */
int bcm_mips_attach(device_t dev, u_int num_cpuirqs, u_int timer_irq,
driver_filter_t filter);
int bcm_mips_detach(device_t dev);
/**
* Broadcom MIPS PIC interrupt map data.
*/
struct bcm_mips_intr_map_data {
struct intr_map_data mdata;
u_int ivec; /**< bus interrupt vector */
};
/**
* Nested MIPS CPU interrupt handler state.
*/
struct bcm_mips_cpuirq {
struct bcm_mips_softc *sc; /**< driver instance state, or NULL if uninitialized. */
u_int mips_irq; /**< mips hardware interrupt number (relative to NSOFT_IRQ) */
int irq_rid; /**< mips IRQ resource id, or -1 if this entry is unavailable */
struct resource *irq_res; /**< mips interrupt resource */
void *irq_cookie; /**< mips interrupt handler cookie, or NULL */
struct bcm_mips_irqsrc *isrc_solo; /**< solo isrc assigned to this interrupt, or NULL */
u_int refs; /**< isrc consumer refcount */
};
/**
* Broadcom MIPS PIC interrupt source definition.
*/
struct bcm_mips_irqsrc {
struct intr_irqsrc isrc;
u_int ivec; /**< bus interrupt vector */
u_int refs; /**< active reference count */
struct bcm_mips_cpuirq *cpuirq; /**< assigned MIPS HW IRQ, or NULL if no assignment */
};
/**
* bcm_mips driver instance state. Must be first member of all subclass
* softc structures.
*/
struct bcm_mips_softc {
device_t dev;
struct bcm_mips_cpuirq cpuirqs[NREAL_IRQS]; /**< nested CPU IRQ handlers */
u_int num_cpuirqs; /**< number of nested CPU IRQ handlers */
u_int timer_irq; /**< CPU timer IRQ */
struct bcm_mips_irqsrc isrcs[BCM_MIPS_NINTR];
struct mtx mtx;
};
#define BCM_MIPS_IVEC_MASK(_isrc) (1 << ((_isrc)->ivec))
#define BCM_MIPS_LOCK_INIT(sc) \
mtx_init(&(sc)->mtx, device_get_nameunit((sc)->dev), \
"bhnd mips driver lock", MTX_DEF)
#define BCM_MIPS_LOCK(sc) mtx_lock(&(sc)->mtx)
#define BCM_MIPS_UNLOCK(sc) mtx_unlock(&(sc)->mtx)
#define BCM_MIPS_LOCK_ASSERT(sc, what) mtx_assert(&(sc)->mtx, what)
#define BCM_MIPS_LOCK_DESTROY(sc) mtx_destroy(&(sc)->mtx)
#endif /* _MIPS_BROADCOM_BCM_MIPSVAR_H_ */

View File

@ -45,7 +45,9 @@ __FBSDID("$FreeBSD$");
#include <dev/bhnd/bhnd_ids.h>
#include <dev/bhnd/bcma/bcmavar.h>
#include <dev/bhnd/bcma/bcma_dmp.h>
#include "bcm_mipsvar.h"
#include "bcm_machdep.h"
#include "bhnd_nexusvar.h"
@ -57,6 +59,9 @@ __FBSDID("$FreeBSD$");
static int bcma_nexus_attach(device_t);
static int bcma_nexus_probe(device_t);
_Static_assert(BCMA_OOB_NUM_BUSLINES == BCM_MIPS_NINTR, "BCMA incompatible "
"with generic NINTR");
static int
bcma_nexus_probe(device_t dev)
{

View File

@ -44,6 +44,7 @@ __FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/intr.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/rman.h>
@ -55,6 +56,7 @@ __FBSDID("$FreeBSD$");
#include <dev/bhnd/bhnd_ids.h>
#include "bcm_machdep.h"
#include "bcm_mipsvar.h"
#include "bhnd_nexusvar.h"
@ -149,28 +151,47 @@ bhnd_nexus_get_chipid(device_t dev, device_t child)
}
/**
* Default bhnd_nexus implementation of BHND_BUS_GET_INTR_COUNT().
* Default bhnd_nexus implementation of BHND_BUS_MAP_INTR().
*/
static int
bhnd_nexus_get_intr_count(device_t dev, device_t child)
bhnd_nexus_map_intr(device_t dev, device_t child, u_int intr, rman_res_t *irq)
{
// TODO: arch-specific interrupt handling.
struct bcm_mips_intr_map_data *imd;
u_int ivec;
uintptr_t xref;
int error;
/* Fetch the backplane interrupt vector */
if ((error = bhnd_get_intr_ivec(child, intr, &ivec))) {
device_printf(dev, "error fetching ivec for intr %u: %d\n",
intr, error);
return (error);
}
/* Determine our interrupt domain */
xref = BHND_BUS_GET_INTR_DOMAIN(dev, child, false);
KASSERT(xref != 0, ("missing interrupt domain"));
/* Allocate our map data */
imd = (struct bcm_mips_intr_map_data *)intr_alloc_map_data(
INTR_MAP_DATA_BCM_MIPS, sizeof(*imd), M_WAITOK | M_ZERO);
imd->ivec = ivec;
/* Map the IRQ */
*irq = intr_map_irq(NULL, xref, &imd->mdata);
return (0);
}
/**
* Default bhnd_nexus implementation of BHND_BUS_ASSIGN_INTR().
* Default bhnd_nexus implementation of BHND_BUS_UNMAP_INTR().
*/
static int
bhnd_nexus_assign_intr(device_t dev, device_t child, int rid)
static void
bhnd_nexus_unmap_intr(device_t dev, device_t child, rman_res_t irq)
{
uint32_t ivec;
int error;
if (irq > UINT_MAX)
panic("invalid irq: %ju", (uintmax_t)irq);
if ((error = bhnd_get_core_ivec(child, rid, &ivec)))
return (error);
return (bus_set_resource(child, SYS_RES_IRQ, rid, ivec, 1));
intr_unmap_irq(irq);
}
static device_method_t bhnd_nexus_methods[] = {
@ -185,8 +206,9 @@ static device_method_t bhnd_nexus_methods[] = {
DEVMETHOD(bhnd_bus_is_hw_disabled, bhnd_nexus_is_hw_disabled),
DEVMETHOD(bhnd_bus_get_attach_type, bhnd_nexus_get_attach_type),
DEVMETHOD(bhnd_bus_get_chipid, bhnd_nexus_get_chipid),
DEVMETHOD(bhnd_bus_get_intr_count, bhnd_nexus_get_intr_count),
DEVMETHOD(bhnd_bus_assign_intr, bhnd_nexus_assign_intr),
DEVMETHOD(bhnd_bus_get_intr_domain, bhnd_bus_generic_get_intr_domain),
DEVMETHOD(bhnd_bus_map_intr, bhnd_nexus_map_intr),
DEVMETHOD(bhnd_bus_unmap_intr, bhnd_nexus_unmap_intr),
DEVMETHOD_END
};

View File

@ -7,6 +7,8 @@
mips/broadcom/bcm_machdep.c standard
mips/broadcom/bcm_bmips.c optional siba_nexus siba
mips/broadcom/bcm_mips74k.c optional bcma_nexus bcma
mips/broadcom/bcm_mips.c optional siba_nexus siba | \
bcma_nexus bcma
mips/broadcom/bcm_nvram_cfe.c optional bhnd siba_nexus cfe | \
bhnd bcma_nexus cfe
mips/broadcom/bcm_pmu.c standard

View File

@ -39,9 +39,11 @@ __FBSDID("$FreeBSD$");
#include <dev/bhnd/bhnd_ids.h>
#include <dev/bhnd/siba/sibareg.h>
#include <dev/bhnd/siba/sibavar.h>
#include "bcm_machdep.h"
#include "bcm_mipsvar.h"
#include "bhnd_nexusvar.h"
@ -51,6 +53,9 @@ __FBSDID("$FreeBSD$");
* Derived from Bruce M. Simpson' original siba(4) driver.
*/
_Static_assert(SIBA_MAX_INTR == BCM_MIPS_NINTR, "SIBA incompatible with "
"generic NINTR");
static int
siba_nexus_probe(device_t dev)
{

View File

@ -59,6 +59,10 @@
#define MIPS_PIC_XREF 1 /**< unique xref */
#endif
#define NHARD_IRQS 6
#define NSOFT_IRQS 2
#define NREAL_IRQS (NHARD_IRQS + NSOFT_IRQS)
#define INTR_IRQ_NSPC_SWI 4
/* MIPS32 PIC APIs */

View File

@ -70,10 +70,6 @@ __FBSDID("$FreeBSD$");
#include "pic_if.h"
#define NHARD_IRQS 6
#define NSOFT_IRQS 2
#define NREAL_IRQS (NHARD_IRQS + NSOFT_IRQS)
struct mips_pic_softc;
static int mips_pic_intr(void *);