Page MenuHomeFreeBSD

D12518.id35053.diff
No OneTemporary

D12518.id35053.diff

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, &reg) == 0)
- sc->pci_devclass = BHND_DEVCLASS_PCIE;
- else
- sc->pci_devclass = BHND_DEVCLASS_PCI;
+ sc->isrc = bhndb_alloc_intr_isrc(sc->parent, irq_rid, 0, RM_MAX_END, 1,
+ RF_SHAREABLE | RF_ACTIVE);
+ if (sc->isrc == NULL) {
+ device_printf(sc->dev, "failed to allocate interrupt "
+ "resource\n");
+ error = ENXIO;
+ goto cleanup;
+ }
/* Enable clocks (if required by this hardware) */
if ((error = bhndb_enable_pci_clocks(sc->dev)))
@@ -226,12 +374,14 @@
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, &region_addr, &region_size);
+ if (error)
+ return (error);
+
+ /* Verify that our offset fits within the region */
+ if (region_size < cfg_size) {
+ printf("%s%u.%u offset %ju exceeds %s0.0 size %ju\n",
+ bhnd_port_type_name(type), port, region, cfg_offset,
+ bhnd_port_type_name(BHND_PORT_DEVICE), region_size);
+
+ return (ENXIO);
+ }
+
+ if (BHND_ADDR_MAX - region_addr < cfg_offset) {
+ printf("%s%u.%u offset %ju would overflow %s0.0 addr "
+ "%ju\n", bhnd_port_type_name(type), port, region,
+ cfg_offset, bhnd_port_type_name(BHND_PORT_DEVICE),
+ region_addr);
+
+ return (ENXIO);
+ }
+
+ if (info != NULL)
+ *info = core;
+
+ *addr = region_addr + cfg_offset;
+ *size = cfg_size;
+ return (0);
+ }
+
+ /*
+ * Otherwise, must be a device port.
+ *
+ * Map the bhnd device port to a siba addrspace index. Unlike siba(4)
+ * bus drivers, we do not exclude the siba(4) configuration blocks from
+ * the first device port.
+ */
+ error = siba_addrspace_index(&sid, type, port, region, &addrspace);
if (error)
return (error);
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

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)

Event Timeline