Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F138047573
D12518.id35053.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
199 KB
Referenced Files
None
Subscribers
None
D12518.id35053.diff
View Options
Index: sys/dev/bhnd/bcma/bcma.c
===================================================================
--- sys/dev/bhnd/bcma/bcma.c
+++ sys/dev/bhnd/bcma/bcma.c
@@ -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 @@
/* 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,66 +617,46 @@
/**
* 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;
-
- dinfo = device_get_ivars(child);
-
- /* Agent block must be mapped */
- if (dinfo->res_agent == NULL)
- return (0);
+ struct bcma_devinfo *dinfo;
- /* 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);
+ /* 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));
- /* 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);
+ dinfo = device_get_ivars(child);
+ 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;
- dinfo = device_get_ivars(child);
+ /* 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));
+ }
- /* Interrupt ID must be valid. */
- if (intr >= bcma_get_intr_count(dev, child))
- return (ENXIO);
+ dinfo = device_get_ivars(child);
- /* 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);
+ }
+ }
- return (0);
+ /* Not found */
+ return (ENXIO);
}
/**
@@ -707,8 +691,6 @@
/* 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 @@
/* 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 @@
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
};
Index: sys/dev/bhnd/bcma/bcma_subr.c
===================================================================
--- sys/dev/bhnd/bcma/bcma_subr.c
+++ sys/dev/bhnd/bcma/bcma_subr.c
@@ -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 @@
* @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,6 +197,126 @@
}
+
+/**
+ * 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.
+ *
+ * @param bus The requesting bus device.
+ * @param child The bcma child device.
+ * @param dinfo The device info associated with @p child
+ *
+ * @retval 0 success
+ * @retval non-zero resource allocation failed.
+ */
+static int
+bcma_dinfo_init_agent(device_t bus, device_t child, struct bcma_devinfo *dinfo)
+{
+ bhnd_addr_t addr;
+ bhnd_size_t size;
+ rman_res_t r_start, r_count, r_end;
+ int error;
+
+ KASSERT(dinfo->res_agent == NULL, ("double allocation of agent"));
+
+ /* Verify that the agent register block exists and is
+ * mappable */
+ if (bhnd_get_port_rid(child, BHND_PORT_AGENT, 0, 0) == -1)
+ return (0); /* nothing to do */
+
+ /* Fetch the address of the agent register block */
+ error = bhnd_get_region_addr(child, BHND_PORT_AGENT, 0, 0,
+ &addr, &size);
+ if (error) {
+ device_printf(bus, "failed fetching agent register block "
+ "address for core %u\n", BCMA_DINFO_COREIDX(dinfo));
+ return (error);
+ }
+
+ /* Allocate the resource */
+ r_start = addr;
+ r_count = size;
+ r_end = r_start + r_count - 1;
+
+ 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|RF_SHAREABLE);
+ if (dinfo->res_agent == NULL) {
+ device_printf(bus, "failed allocating agent register block for "
+ "core %u\n", BCMA_DINFO_COREIDX(dinfo));
+ return (ENXIO);
+ }
+
+ 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.
*
@@ -213,6 +337,9 @@
dinfo->res_agent = NULL;
dinfo->rid_agent = -1;
+ STAILQ_INIT(&dinfo->intrs);
+ dinfo->num_intrs = 0;
+
resource_list_init(&dinfo->resources);
return (dinfo);
@@ -224,7 +351,8 @@
* configuration.
*
* @param bus The requesting bus device.
- * @param dinfo The device info instance.
+ * @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.
*
@@ -232,9 +360,12 @@
* @retval non-zero initialization failed.
*/
int
-bcma_init_dinfo(device_t bus, struct bcma_devinfo *dinfo,
+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 */
@@ -242,71 +373,52 @@
/* 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().
- *
- * If an agent0.0 region is not defined on @p dinfo, the device info
- * agent resource is set to NULL and 0 is returned.
- *
- * @param bus The requesting bus device.
- * @param child The bcma child device.
- * @param dinfo The device info associated with @p child
- *
- * @retval 0 success
- * @retval non-zero resource allocation failed.
- */
-int
-bcma_dinfo_alloc_agent(device_t bus, device_t child, struct bcma_devinfo *dinfo)
-{
- bhnd_addr_t addr;
- bhnd_size_t size;
- rman_res_t r_start, r_count, r_end;
- int error;
+ 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;
+ }
- KASSERT(dinfo->res_agent == NULL, ("double allocation of agent"));
+ intr->i_mapped = true;
- /* Verify that the agent register block exists and is
- * mappable */
- if (bhnd_get_port_rid(child, BHND_PORT_AGENT, 0, 0) == -1)
- return (0); /* nothing to do */
-
- /* Fetch the address of the agent register block */
- error = bhnd_get_region_addr(child, BHND_PORT_AGENT, 0, 0,
- &addr, &size);
- if (error) {
- device_printf(bus, "failed fetching agent register block "
- "address for core %u\n", BCMA_DINFO_COREIDX(dinfo));
- return (error);
+ /* Add to resource list */
+ intr->i_rid = resource_list_add_next(&dinfo->resources,
+ SYS_RES_IRQ, intr->i_irq, intr->i_irq, 1);
}
- /* Allocate the resource */
- r_start = addr;
- r_count = size;
- r_end = r_start + r_count - 1;
+ return (0);
- 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);
- if (dinfo->res_agent == NULL) {
- device_printf(bus, "failed allocating agent register block for "
- "core %u\n", BCMA_DINFO_COREIDX(dinfo));
- return (ENXIO);
- }
+failed:
+ /* Owned by the caller on failure */
+ dinfo->corecfg = NULL;
- return (0);
+ return (error);
}
-
/**
* Deallocate the given device info structure and any associated resources.
*
@@ -314,8 +426,10 @@
* @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 @@
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.
*
Index: sys/dev/bhnd/bcma/bcmavar.h
===================================================================
--- sys/dev/bhnd/bcma/bcmavar.h
+++ sys/dev/bhnd/bcma/bcmavar.h
@@ -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 @@
struct bcma_devinfo;
struct bcma_corecfg;
+struct bcma_intr;
struct bcma_map;
struct bcma_mport;
struct bcma_sport;
@@ -74,8 +79,8 @@
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 @@
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 @@
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) */
@@ -131,8 +150,9 @@
STAILQ_ENTRY(bcma_sport) sp_link;
};
-STAILQ_HEAD(bcma_mport_list, bcma_mport);
-STAILQ_HEAD(bcma_sport_list, 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 */
struct bcma_corecfg {
@@ -162,6 +182,9 @@
* 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 */
};
Index: sys/dev/bhnd/bhnd.h
===================================================================
--- sys/dev/bhnd/bhnd.h
+++ sys/dev/bhnd/bhnd.h
@@ -250,10 +250,10 @@
{{ 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_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 @@
}
/**
- * 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 @@
* 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.
*
Index: sys/dev/bhnd/bhnd.c
===================================================================
--- sys/dev/bhnd/bhnd.c
+++ sys/dev/bhnd/bhnd.c
@@ -814,6 +814,22 @@
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 @@
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),
Index: sys/dev/bhnd/bhnd_bus_if.m
===================================================================
--- sys/dev/bhnd/bhnd_bus_if.m
+++ sys/dev/bhnd/bhnd_bus_if.m
@@ -141,25 +141,6 @@
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 @@
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 @@
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 @@
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.
Index: sys/dev/bhnd/bhnd_ids.h
===================================================================
--- sys/dev/bhnd/bhnd_ids.h
+++ sys/dev/bhnd/bhnd_ids.h
@@ -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 */
Index: sys/dev/bhnd/bhnd_match.h
===================================================================
--- sys/dev/bhnd/bhnd_match.h
+++ sys/dev/bhnd/bhnd_match.h
@@ -154,7 +154,8 @@
chip_id:1,
chip_rev:1,
chip_pkg:1,
- flags_unused:5;
+ chip_type:1,
+ flags_unused:4;
} match;
} m;
@@ -162,38 +163,46 @@
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_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 @@
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 @@
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 */
Index: sys/dev/bhnd/bhnd_subr.c
===================================================================
--- sys/dev/bhnd/bhnd_subr.c
+++ sys/dev/bhnd/bhnd_subr.c
@@ -738,6 +738,9 @@
!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 @@
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);
+}
\ No newline at end of file
Index: sys/dev/bhnd/bhndb/bhnd_bhndb.c
===================================================================
--- sys/dev/bhnd/bhndb/bhnd_bhndb.c
+++ sys/dev/bhnd/bhndb/bhnd_bhndb.c
@@ -98,10 +98,17 @@
}
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 @@
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),
Index: sys/dev/bhnd/bhndb/bhndb.c
===================================================================
--- sys/dev/bhnd/bhndb/bhndb.c
+++ sys/dev/bhnd/bhndb/bhndb.c
@@ -804,7 +804,7 @@
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 @@
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,12 +1285,25 @@
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 @@
/**
* 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,16 +1442,27 @@
if ((error = rman_deactivate_resource(r)))
return (error);
- /* Free any dynamic window allocation. */
- if (bhndb_get_addrspace(sc, child) == BHNDB_ADDRSPACE_BRIDGED) {
- BHNDB_LOCK(sc);
- dwa = bhndb_dw_find_resource(sc->bus_res, r);
- if (dwa != NULL)
- bhndb_dw_release(sc->bus_res, dwa, r);
- BHNDB_UNLOCK(sc);
- }
+ switch (type) {
+ case SYS_RES_IRQ:
+ /* No bridge-level state to be freed */
+ return (0);
- return (0);
+ case SYS_RES_MEMORY:
+ /* Free any dynamic window allocation. */
+ if (bhndb_get_addrspace(sc, child) == BHNDB_ADDRSPACE_BRIDGED) {
+ BHNDB_LOCK(sc);
+ dwa = bhndb_dw_find_resource(sc->bus_res, r);
+ if (dwa != NULL)
+ bhndb_dw_release(sc->bus_res, dwa, r);
+ BHNDB_UNLOCK(sc);
+ }
+
+ return (0);
+
+ default:
+ device_printf(dev, "unsupported resource type %d\n", type);
+ return (ENXIO);
+ }
}
/**
@@ -1457,12 +1478,15 @@
/**
* 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 @@
{
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 @@
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;
-
- 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 (r_prio < sc->bus_res->min_prio)
- return (0);
+ switch (type) {
+ case SYS_RES_IRQ:
+ /* IRQ resources are always direct */
+ break;
+
+ 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 (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_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 @@
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 @@
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),
Index: sys/dev/bhnd/bhndb/bhndb_if.m
===================================================================
--- sys/dev/bhnd/bhndb/bhndb_if.m
+++ sys/dev/bhnd/bhndb/bhndb_if.m
@@ -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 @@
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 @@
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 @@
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;
Index: sys/dev/bhnd/bhndb/bhndb_pci.c
===================================================================
--- sys/dev/bhnd/bhndb/bhndb_pci.c
+++ sys/dev/bhnd/bhndb/bhndb_pci.c
@@ -64,6 +64,8 @@
#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 @@
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_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 @@
#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 @@
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 @@
static int
bhndb_pci_probe(device_t dev)
{
- device_t parent;
- devclass_t parent_bus;
- devclass_t pci;
+ 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, pci;
+ int error;
+
+ cores = NULL;
/* Our parent must be a PCI/PCIe device. */
pci = devclass_find("pci");
@@ -140,35 +244,67 @@
if (parent_bus != pci)
return (ENXIO);
+ /* Enable clocks */
+ if ((error = bhndb_enable_pci_clocks(dev)))
+ return (error);
+
+ /* 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;
+ }
+
device_set_desc(dev, "PCI-BHND bridge");
- return (BUS_PROBE_DEFAULT);
+ /* fall-through */
+ error = BUS_PROBE_DEFAULT;
+
+cleanup:
+ bhndb_disable_pci_clocks(dev);
+ if (cores != NULL)
+ free(cores, M_BHND);
+
+ return (error);
}
-/* Configure MSI interrupts */
+/**
+ * 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 @@
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, ®) == 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 @@
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 @@
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 @@
pci_disable_busmaster(sc->parent);
+ BHNDB_PCI_LOCK_DESTROY(sc);
+
return (error);
}
@@ -287,13 +442,18 @@
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 @@
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 @@
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");
+ if ((sc->pci_quirks & BHNDB_PCI_QUIRK_SRSH_WAR) == 0)
return;
- }
- if (hostb_core.vendor != BHND_MFGID_BCM)
- 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;
- }
-
- /* 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;
- }
+ /* 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"));
- /* 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 @@
}
/**
- * 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 @@
return (bhndb_enable_pci_clocks(sc->dev));
}
+/**
+ * BHNDB_MAP_INTR_ISRC()
+ */
+static int
+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_assign_intr(device_t dev, device_t child, int rid)
+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);
-
- /* 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);
+ if (sc->pci_quirks & BHNDB_PCI_QUIRK_SIBA_INTVEC)
+ return (bhndb_pci_route_siba_interrupts(sc, child));
- /* Add to child's resource list */
- return (bus_set_resource(child, SYS_RES_IRQ, rid, start, count));
+ 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);
+ }
+
+ 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 @@
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 @@
/* 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
};
Index: sys/dev/bhnd/bhndb/bhndb_pcireg.h
===================================================================
--- sys/dev/bhnd/bhndb/bhndb_pcireg.h
+++ sys/dev/bhnd/bhndb/bhndb_pcireg.h
@@ -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 */
Index: sys/dev/bhnd/bhndb/bhndb_pcivar.h
===================================================================
--- sys/dev/bhnd/bhndb/bhndb_pcivar.h
+++ sys/dev/bhnd/bhndb/bhndb_pcivar.h
@@ -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 @@
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 */
+ 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 */
+ uint32_t pci_quirks; /**< PCI bridge-level quirks */
+ int msi_count; /**< MSI count, or 0 */
+ struct bhndb_intr_isrc *isrc; /**< host interrupt source */
- bhndb_pci_set_regwin_t set_regwin; /**< regwin handler */
+ 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_ */
Index: sys/dev/bhnd/bhndb/bhndb_private.h
===================================================================
--- sys/dev/bhnd/bhndb/bhndb_private.h
+++ sys/dev/bhnd/bhndb/bhndb_private.h
@@ -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 @@
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 @@
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 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 @@
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 */
};
/**
Index: sys/dev/bhnd/bhndb/bhndb_subr.c
===================================================================
--- sys/dev/bhnd/bhndb/bhndb_subr.c
+++ sys/dev/bhnd/bhndb/bhndb_subr.c
@@ -270,10 +270,11 @@
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 @@
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 @@
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 @@
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);
@@ -477,9 +501,14 @@
void
bhndb_free_resources(struct bhndb_resources *br)
{
- struct bhndb_region *region, *r_next;
- struct bhndb_dw_alloc *dwa;
- struct bhndb_dw_rentry *dwr, *dwr_next;
+ struct bhndb_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 @@
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 @@
/* 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 @@
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 @@
}
-/**
- * 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.
*
Index: sys/dev/bhnd/bhndb/bhndbvar.h
===================================================================
--- sys/dev/bhnd/bhndb/bhndbvar.h
+++ sys/dev/bhnd/bhndb/bhndbvar.h
@@ -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 @@
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 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 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_ */
Index: sys/dev/bhnd/bhndvar.h
===================================================================
--- sys/dev/bhnd/bhndvar.h
+++ sys/dev/bhnd/bhndvar.h
@@ -86,6 +86,12 @@
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,
Index: sys/dev/bhnd/cores/chipc/chipc.c
===================================================================
--- sys/dev/bhnd/cores/chipc/chipc.c
+++ sys/dev/bhnd/cores/chipc/chipc.c
@@ -112,9 +112,6 @@
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,10 +271,13 @@
}
/* 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);
+ }
}
/*
@@ -300,6 +300,11 @@
/* 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 @@
}
/* 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,16 +342,24 @@
}
/* 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 @@
}
resource_list_init(&dinfo->resources);
+ dinfo->irq_mapped = false;
device_set_ivars(child, dinfo);
return (child);
@@ -603,7 +621,15 @@
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 @@
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:
Index: sys/dev/bhnd/cores/chipc/chipc_private.h
===================================================================
--- sys/dev/bhnd/cores/chipc/chipc_private.h
+++ sys/dev/bhnd/cores/chipc/chipc_private.h
@@ -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 @@
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,
Index: sys/dev/bhnd/cores/chipc/chipc_subr.c
===================================================================
--- sys/dev/bhnd/cores/chipc/chipc_subr.c
+++ sys/dev/bhnd/cores/chipc/chipc_subr.c
@@ -162,22 +162,69 @@
}
/**
- * Associate a resource with a given resource ID, relative to the given
- * port and region.
- *
- * This function behaves identically to bus_set_resource() for all resource
- * types other than SYS_RES_MEMORY.
+ * Map an interrupt line to an IRQ, and then register a corresponding SYS_RES_IRQ
+ * with @p child's resource list.
+ *
+ * @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 @@
* @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 @@
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 @@
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));
}
Index: sys/dev/bhnd/cores/chipc/chipcvar.h
===================================================================
--- sys/dev/bhnd/cores/chipc/chipcvar.h
+++ sys/dev/bhnd/cores/chipc/chipcvar.h
@@ -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 @@
*/
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 */
};
/**
Index: sys/dev/bhnd/cores/pci/bhnd_pci_hostb.c
===================================================================
--- sys/dev/bhnd/cores/pci/bhnd_pci_hostb.c
+++ sys/dev/bhnd/cores/pci/bhnd_pci_hostb.c
@@ -139,13 +139,13 @@
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 |
Index: sys/dev/bhnd/cores/usb/bhnd_usb.c
===================================================================
--- sys/dev/bhnd/cores/usb/bhnd_usb.c
+++ sys/dev/bhnd/cores/usb/bhnd_usb.c
@@ -137,18 +137,6 @@
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 @@
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 @@
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 @@
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);
- rle->res = NULL;
+ 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 @@
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 @@
/* 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),
Index: sys/dev/bhnd/cores/usb/bhnd_usbvar.h
===================================================================
--- sys/dev/bhnd/cores/usb/bhnd_usbvar.h
+++ sys/dev/bhnd/cores/usb/bhnd_usbvar.h
@@ -50,7 +50,8 @@
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;
Index: sys/dev/bhnd/siba/siba.c
===================================================================
--- sys/dev/bhnd/siba/siba.c
+++ sys/dev/bhnd/siba/siba.c
@@ -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 @@
/* 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 @@
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 @@
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 @@
/* 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 @@
/* 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 @@
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 @@
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 @@
{
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 @@
port_type, port_num, region_num));
dinfo = device_get_ivars(child);
+
+ /* Look for a matching addrspace entry */
addrspace = siba_find_addrspace(dinfo, port_type, port_num, region_num);
- if (addrspace == NULL)
- return (-1);
+ if (addrspace != NULL)
+ return (addrspace->sa_rid);
- 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 @@
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 @@
{
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,67 +616,72 @@
}
dinfo = device_get_ivars(child);
+
+ /* Look for a matching addrspace */
addrspace = siba_find_addrspace(dinfo, port_type, port_num, region_num);
- if (addrspace == NULL)
- return (ENOENT);
+ if (addrspace != NULL) {
+ *addr = addrspace->sa_base;
+ *size = addrspace->sa_size - addrspace->sa_bus_reserved;
+ return (0);
+ }
- *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;
+ struct siba_devinfo *dinfo;
/* 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);
-
- /* 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 */
+ KASSERT(intr == 0, ("invalid ivec %u", intr));
+
dinfo = device_get_ivars(child);
- tpsflag = bhnd_bus_read_4(dinfo->cfg[0], SIBA_CFG0_TPSFLAG);
- *ivec = SIBA_REG_GET(tpsflag, TPS_NUM0);
+ KASSERT(dinfo->intr_en, ("core does not have an interrupt assigned"));
+ *ivec = dinfo->intr.flag;
return (0);
}
@@ -715,6 +745,55 @@
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 @@
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 @@
* 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 */
+ rid = resource_list_add_next(&dinfo->resources, SYS_RES_MEMORY,
+ r_start, r_end, r_count);
+
+ /* 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[i] = BHND_BUS_ALLOC_RESOURCE(dev, dev,
+ 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);
+ r_count, RF_ACTIVE|RF_SHAREABLE);
- if (dinfo->cfg[i] == NULL) {
+ if (dinfo->cfg_res[i] == NULL) {
device_printf(dev, "failed to allocate SIBA_CFG%hhu\n",
i);
return (ENXIO);
@@ -805,7 +891,7 @@
/* 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 @@
*/
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 @@
}
/* 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 @@
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 @@
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 @@
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 @@
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
};
Index: sys/dev/bhnd/siba/siba_bhndb.c
===================================================================
--- sys/dev/bhnd/siba/siba_bhndb.c
+++ sys/dev/bhnd/siba/siba_bhndb.c
@@ -51,20 +51,17 @@
* 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.
-//
-
-static int siba_bhndb_wars_hwup(struct siba_softc *sc);
-
-/* Bridge-specific core device quirks */
+struct siba_bhndb_softc;
+
+static int siba_bhndb_wars_hwup(struct siba_bhndb_softc *sc);
+
+/* 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 @@
SIBA_QUIRK_PCIE_D11_SB_TIMEOUT = (1<<0)
};
-static struct bhnd_device_quirk 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),
+/* 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 @@
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 @@
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 @@
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 @@
/* 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 @@
/* 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 @@
/* 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 @@
* 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]));
+ int error;
- 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);
}
Index: sys/dev/bhnd/siba/siba_erom.c
===================================================================
--- sys/dev/bhnd/siba/siba_erom.c
+++ sys/dev/bhnd/siba/siba_erom.c
@@ -334,7 +334,8 @@
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 @@
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, ®ion_addr, ®ion_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);
Index: sys/dev/bhnd/siba/siba_subr.c
===================================================================
--- sys/dev/bhnd/siba/siba_subr.c
+++ sys/dev/bhnd/siba/siba_subr.c
@@ -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 @@
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 @@
}
/**
- * 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 @@
}
/**
- * 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) */
@@ -182,64 +195,195 @@
return (addrspace - 1);
}
+/**
+ * Map an config block index to its corresponding bhnd(4) BHND_PORT_AGENT port
+ * number.
+ *
+ * @param cfg Config block index.
+ */
+u_int
+siba_cfg_agent_port(u_int cfg)
+{
+ /* Always agent0 */
+ return (0);
+}
+
+/**
+ * Map an config block index to its corresponding bhnd(4) BHND_PORT_AGENT port
+ * region number.
+ *
+ * @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 num_addrspace.
+ * @p core_id and @p 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 num_addrspace The number of siba address spaces.
+ * @param core_id The siba core info.
+ * @param port_type The bhnd(4) port type.
*/
u_int
-siba_addrspace_port_count(u_int num_addrspace)
+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(struct siba_core_id *core_id, bhnd_port_type port_type,
+ u_int port)
{
- /* 0, 1, or 2 ports */
- return min(num_addrspace, 2);
+ /* Verify the index against the port count */
+ if (siba_port_count(core_id, port_type) <= port)
+ return (false);
+
+ return (true);
}
/**
- * Return the number of bhnd(4) regions to advertise on @p port
- * given the provided @p num_addrspace address space count.
+ * Return the number of bhnd(4) regions to advertise for @p core_id on the
+ * @p port of @p port_type.
*
- * @param num_addrspace The number of core-mapped siba(4) Sonics/OCP address
- * spaces.
+ * @param core_id The siba core info.
+ * @param port_type The bhnd(4) port type.
*/
u_int
-siba_addrspace_region_count(u_int num_addrspace, u_int port)
+siba_port_region_count(struct siba_core_id *core_id, bhnd_port_type port_type,
+ u_int port)
{
- /* The first address space, if any, is mapped to device0.0 */
- if (port == 0)
- return (min(num_addrspace, 1));
+ /* 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;
+ }
- /* All remaining address spaces are mapped to device0.(n - 1) */
- if (port == 1 && num_addrspace >= 2)
- return (num_addrspace - 1);
+ /* Validated above */
+ panic("siba_is_port_valid() returned true for unknown %s.%u port",
+ bhnd_port_type_name(port_type), port);
- /* No region mapping */
+}
+
+/**
+ * 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);
}
/**
- * Return true if @p port is defined given an address space count
- * of @p num_addrspace, false otherwise.
+ * Map an bhnd(4) type/port/region triplet to its associated config block
+ * entry, if any.
*
- * Refer to the siba_find_addrspace() function for information on siba's
- * mapping of bhnd(4) port and region identifiers.
+ * The only supported port type is BHND_PORT_DEVICE.
*
- * @param num_addrspace The number of address spaces to verify the port against.
+ * @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.
*/
-bool
-siba_is_port_valid(u_int num_addrspace, bhnd_port_type type, u_int port)
+struct siba_cfg_block *
+siba_find_cfg_block(struct siba_devinfo *dinfo, bhnd_port_type type, u_int port,
+ u_int region)
{
- /* Only device ports are supported */
- if (type != BHND_PORT_DEVICE)
- return (false);
+ u_int cfgidx;
+ int error;
- /* Verify the index against the port count */
- if (siba_addrspace_port_count(num_addrspace) <= port)
- return (false);
+ /* Map to addrspace index */
+ error = siba_cfg_index(&dinfo->core_id, type, port, region, &cfgidx);
+ if (error)
+ return (NULL);
- return (true);
+ /* Found */
+ return (&dinfo->cfg[cfgidx]);
}
/**
@@ -255,10 +399,8 @@
* 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 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 @@
else
return (ENOENT);
- if (idx >= num_addrspace)
+ if (idx >= core_id->num_addrspace)
return (ENOENT);
/* Found */
@@ -310,8 +457,8 @@
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 @@
* 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 @@
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 @@
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) {
Index: sys/dev/bhnd/siba/sibareg.h
===================================================================
--- sys/dev/bhnd/siba/sibareg.h
+++ sys/dev/bhnd/siba/sibareg.h
@@ -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
Index: sys/dev/bhnd/siba/sibavar.h
===================================================================
--- sys/dev/bhnd/siba/sibavar.h
+++ sys/dev/bhnd/siba/sibavar.h
@@ -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_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 @@
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_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);
-u_int siba_addrspace_port(u_int addrspace);
-u_int siba_addrspace_region(u_int addrspace);
-int siba_addrspace_index(u_int num_addrspace,
+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 @@
* 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 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 */
};
Index: sys/mips/broadcom/bcm_bmips.c
===================================================================
--- sys/mips/broadcom/bcm_bmips.c
+++ sys/mips/broadcom/bcm_bmips.c
@@ -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 @@
#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 @@
* 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;
+ 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;
+ 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;
+ 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),
-
+ 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);
Index: sys/mips/broadcom/bcm_machdep.h
===================================================================
--- sys/mips/broadcom/bcm_machdep.h
+++ sys/mips/broadcom/bcm_machdep.h
@@ -55,12 +55,15 @@
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. */
- struct bhnd_core_info pmu_id; /**< PMU core info */
+ struct bhnd_core_info pmu_id; /**< PMU core info */
uintptr_t pmu_addr; /**< PMU core phys address, or
- 0x0 if no PMU */
+ 0x0 if no PMU */
struct bhnd_pmu_query pmu; /**< PMU query instance */
@@ -122,6 +125,11 @@
#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) \
Index: sys/mips/broadcom/bcm_machdep.c
===================================================================
--- sys/mips/broadcom/bcm_machdep.c
+++ sys/mips/broadcom/bcm_machdep.c
@@ -126,6 +126,13 @@
{ 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 @@
}
}
+ /* 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);
Index: sys/mips/broadcom/bcm_mips.c
===================================================================
--- /dev/null
+++ sys/mips/broadcom/bcm_mips.c
@@ -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);
Index: sys/mips/broadcom/bcm_mips74k.c
===================================================================
--- sys/mips/broadcom/bcm_mips74k.c
+++ sys/mips/broadcom/bcm_mips74k.c
@@ -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 @@
#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);
}
@@ -79,21 +146,40 @@
static int
bcm_mips74k_attach(device_t dev)
{
- struct bcm_mips74k_softc *sc;
+ 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);
+ }
+
+ /* 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);
+ }
- /* Route MIPS timer to IRQ5 */
- bus_write_4(sc->mem_res, BCM_MIPS74K_INTR5_SEL,
- (1<<BCM_MIPS74K_TIMER_IVEC));
+ /* 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 @@
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),
-
+ 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);
Index: sys/mips/broadcom/bcm_mips74kreg.h
===================================================================
--- sys/mips/broadcom/bcm_mips74kreg.h
+++ sys/mips/broadcom/bcm_mips74kreg.h
@@ -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_ */
Index: sys/mips/broadcom/bcm_mipsvar.h
===================================================================
--- /dev/null
+++ sys/mips/broadcom/bcm_mipsvar.h
@@ -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_ */
Index: sys/mips/broadcom/bcma_nexus.c
===================================================================
--- sys/mips/broadcom/bcma_nexus.c
+++ sys/mips/broadcom/bcma_nexus.c
@@ -45,7 +45,9 @@
#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 @@
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)
{
Index: sys/mips/broadcom/bhnd_nexus.c
===================================================================
--- sys/mips/broadcom/bhnd_nexus.c
+++ sys/mips/broadcom/bhnd_nexus.c
@@ -44,6 +44,7 @@
#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 @@
#include <dev/bhnd/bhnd_ids.h>
#include "bcm_machdep.h"
+#include "bcm_mipsvar.h"
#include "bhnd_nexusvar.h"
@@ -149,28 +151,47 @@
}
/**
- * 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 ((error = bhnd_get_core_ivec(child, rid, &ivec)))
- return (error);
+ if (irq > UINT_MAX)
+ panic("invalid irq: %ju", (uintmax_t)irq);
- 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 @@
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
};
Index: sys/mips/broadcom/files.broadcom
===================================================================
--- sys/mips/broadcom/files.broadcom
+++ sys/mips/broadcom/files.broadcom
@@ -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
Index: sys/mips/broadcom/siba_nexus.c
===================================================================
--- sys/mips/broadcom/siba_nexus.c
+++ sys/mips/broadcom/siba_nexus.c
@@ -39,9 +39,11 @@
#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 @@
* 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)
{
Index: sys/mips/include/intr.h
===================================================================
--- sys/mips/include/intr.h
+++ sys/mips/include/intr.h
@@ -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 */
Index: sys/mips/mips/mips_pic.c
===================================================================
--- sys/mips/mips/mips_pic.c
+++ sys/mips/mips/mips_pic.c
@@ -70,10 +70,6 @@
#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 *);
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Nov 29, 11:53 AM (11 h, 44 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
26314058
Default Alt Text
D12518.id35053.diff (199 KB)
Attached To
Mode
D12518: Implement MIPS and PCI(e) interrupt support.
Attached
Detach File
Event Timeline
Log In to Comment