Index: head/sys/dev/bhnd/bhnd.h =================================================================== --- head/sys/dev/bhnd/bhnd.h (revision 326079) +++ head/sys/dev/bhnd/bhnd.h (revision 326080) @@ -1,1634 +1,1758 @@ /*- * Copyright (c) 2015-2016 Landon Fuller * 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: * 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 _BHND_BHND_H_ #define _BHND_BHND_H_ #include #include #include #include #include #include "bhnd_ids.h" #include "bhnd_types.h" #include "bhnd_erom_types.h" #include "bhnd_debug.h" #include "bhnd_bus_if.h" #include "bhnd_match.h" #include "nvram/bhnd_nvram.h" struct bhnd_core_pmu_info; extern devclass_t bhnd_devclass; extern devclass_t bhnd_hostb_devclass; extern devclass_t bhnd_nvram_devclass; #define BHND_CHIPID_MAX_NAMELEN 32 /**< maximum buffer required for a bhnd_format_chip_id() */ /** * bhnd child instance variables */ enum bhnd_device_vars { BHND_IVAR_VENDOR, /**< Designer's JEP-106 manufacturer ID. */ BHND_IVAR_DEVICE, /**< Part number */ BHND_IVAR_HWREV, /**< Core revision */ BHND_IVAR_DEVICE_CLASS, /**< Core class (@sa bhnd_devclass_t) */ BHND_IVAR_VENDOR_NAME, /**< Core vendor name */ BHND_IVAR_DEVICE_NAME, /**< Core name */ BHND_IVAR_CORE_INDEX, /**< Bus-assigned core number */ BHND_IVAR_CORE_UNIT, /**< Bus-assigned core unit number, assigned sequentially (starting at 0) for each vendor/device pair. */ BHND_IVAR_PMU_INFO, /**< Internal bus-managed PMU state */ }; /** * bhnd device probe priority bands. */ enum { BHND_PROBE_ROOT = 0, /**< Nexus or host bridge */ BHND_PROBE_BUS = 1000, /**< Buses and bridges */ BHND_PROBE_CPU = 2000, /**< CPU devices */ BHND_PROBE_INTERRUPT = 3000, /**< Interrupt controllers. */ BHND_PROBE_TIMER = 4000, /**< Timers and clocks. */ BHND_PROBE_RESOURCE = 5000, /**< Resource discovery (including NVRAM/SPROM) */ BHND_PROBE_DEFAULT = 6000, /**< Default device priority */ }; /** * Constants defining fine grained ordering within a BHND_PROBE_* priority band. * * Example: * @code * BHND_PROBE_BUS + BHND_PROBE_ORDER_FIRST * @endcode */ enum { BHND_PROBE_ORDER_FIRST = 0, BHND_PROBE_ORDER_EARLY = 25, BHND_PROBE_ORDER_MIDDLE = 50, BHND_PROBE_ORDER_LATE = 75, BHND_PROBE_ORDER_LAST = 100 }; /** * Per-core IOCTL flags common to all bhnd(4) cores. */ enum { BHND_IOCTL_BIST = 0x8000, /**< Initiate a built-in self-test (BIST). Must be cleared after BIST results are read via BHND_IOST_BIST_* */ BHND_IOCTL_PME = 0x4000, /**< Enable posting of power management events by the core. */ BHND_IOCTL_CFLAGS = 0x3FFC, /**< Reserved for core-specific ioctl flags. */ BHND_IOCTL_CLK_FORCE = 0x0002, /**< Force disable of clock gating, resulting in all clocks being distributed within the core. Should be set when asserting/deasserting reset to ensure the reset signal fully propagates to the entire core. */ BHND_IOCTL_CLK_EN = 0x0001, /**< If cleared, the core clock will be disabled. Should be set during normal operation, and cleared when the core is held in reset. */ }; /** * Per-core IOST flags common to all bhnd(4) cores. */ enum { BHND_IOST_BIST_DONE = 0x8000, /**< Set upon BIST completion (see BHND_IOCTL_BIST), and cleared if 0 is written to BHND_IOCTL_BIST. */ BHND_IOST_BIST_FAIL = 0x4000, /**< Set upon detection of a BIST error; the value is unspecified if BIST has not completed and BHND_IOST_BIST_DONE is not set. */ BHND_IOST_CLK = 0x2000, /**< Set if the core has requested that gated clocks be enabled, or cleared otherwise. The value is undefined if a core does not support clock gating. */ BHND_IOST_DMA64 = 0x1000, /**< Set if this core supports 64-bit DMA */ BHND_IOST_CFLAGS = 0x0FFC, /**< Reserved for core-specific status flags. */ }; /* * Simplified accessors for bhnd device ivars */ #define BHND_ACCESSOR(var, ivar, type) \ __BUS_ACCESSOR(bhnd, var, BHND, ivar, type) BHND_ACCESSOR(vendor, VENDOR, uint16_t); BHND_ACCESSOR(device, DEVICE, uint16_t); BHND_ACCESSOR(hwrev, HWREV, uint8_t); BHND_ACCESSOR(class, DEVICE_CLASS, bhnd_devclass_t); BHND_ACCESSOR(vendor_name, VENDOR_NAME, const char *); BHND_ACCESSOR(device_name, DEVICE_NAME, const char *); BHND_ACCESSOR(core_index, CORE_INDEX, u_int); BHND_ACCESSOR(core_unit, CORE_UNIT, int); BHND_ACCESSOR(pmu_info, PMU_INFO, struct bhnd_core_pmu_info *); #undef BHND_ACCESSOR /** * A bhnd(4) board descriptor. */ struct bhnd_board_info { uint16_t board_vendor; /**< PCI-SIG vendor ID (even on non-PCI * devices). * * On PCI devices, this will generally * be the subsystem vendor ID, but the * value may be overridden in device * NVRAM. */ uint16_t board_type; /**< Board type (See BHND_BOARD_*) * * On PCI devices, this will generally * be the subsystem device ID, but the * value may be overridden in device * NVRAM. */ uint16_t board_rev; /**< Board revision. */ uint8_t board_srom_rev; /**< Board SROM format revision */ uint32_t board_flags; /**< Board flags (see BHND_BFL_*) */ uint32_t board_flags2; /**< Board flags 2 (see BHND_BFL2_*) */ uint32_t board_flags3; /**< Board flags 3 (see BHND_BFL3_*) */ }; /** * Chip Identification * * This is read from the ChipCommon ID register; on earlier bhnd(4) devices * where ChipCommon is unavailable, known values must be supplied. */ struct bhnd_chipid { uint16_t chip_id; /**< chip id (BHND_CHIPID_*) */ uint8_t chip_rev; /**< chip revision */ uint8_t chip_pkg; /**< chip package (BHND_PKGID_*) */ uint8_t chip_type; /**< chip type (BHND_CHIPTYPE_*) */ bhnd_addr_t enum_addr; /**< chip_type-specific enumeration * address; either the siba(4) base * core register block, or the bcma(4) * EROM core address. */ uint8_t ncores; /**< number of cores, if known. 0 if * not available. */ }; /** * A bhnd(4) core descriptor. */ struct bhnd_core_info { uint16_t vendor; /**< JEP-106 vendor (BHND_MFGID_*) */ uint16_t device; /**< device */ uint16_t hwrev; /**< hardware revision */ u_int core_idx; /**< bus-assigned core index */ int unit; /**< bus-assigned core unit */ }; /** + * bhnd(4) DMA address widths. + */ +typedef enum { + BHND_DMA_ADDR_30BIT = 30, /**< 30-bit DMA */ + BHND_DMA_ADDR_32BIT = 32, /**< 32-bit DMA */ + BHND_DMA_ADDR_64BIT = 64, /**< 64-bit DMA */ +} bhnd_dma_addrwidth; + +/** + * Convert an address width (in bits) to its corresponding mask. + */ +#define BHND_DMA_ADDR_BITMASK(_width) \ + ((_width >= 64) ? ~0ULL : \ + (_width == 0) ? 0x0 : \ + ((1ULL << (_width)) - 1)) \ + +/** + * bhnd(4) DMA address translation descriptor. + */ +struct bhnd_dma_translation { + /** + * Host-to-device physical address translation. + * + * This may be added to the host physical address to produce a device + * DMA address. + */ + bhnd_addr_t base_addr; + + /** + * Device-addressable address mask. + * + * This defines the device's DMA address range, excluding any bits + * reserved for mapping the address to the base_addr. + */ + bhnd_addr_t addr_mask; + + /** + * Device-addressable extended address mask. + * + * If a per-core bhnd(4) DMA engine supports the 'addrext' control + * field, it can be used to provide address bits excluded by addr_mask. + * + * Support for DMA extended address changes – including coordination + * with the core providing DMA translation – is handled transparently by + * the DMA engine. For example, on PCI(e) Wi-Fi chipsets, the Wi-Fi + * core DMA engine will (in effect) update the PCI core's DMA + * sbtopcitranslation base address to map the full address prior to + * performing a DMA transaction. + */ + bhnd_addr_t addrext_mask; + + /** + * Translation flags (see bhnd_dma_translation_flags) + */ + uint32_t flags; +}; + +#define BHND_DMA_TRANSLATION_TABLE_END { 0, 0, 0, 0 } + +#define BHND_DMA_IS_TRANSLATION_TABLE_END(_dt) \ + ((_dt)->base_addr == 0 && (_dt)->addr_mask == 0 && \ + (_dt)->addrext_mask == 0 && (_dt)->flags == 0) + +/** + * bhnd(4) DMA address translation flags. + */ +enum bhnd_dma_translation_flags { + /** + * The translation remaps the device's physical address space. + * + * This is used in conjunction with BHND_DMA_TRANSLATION_BYTESWAPPED to + * define a DMA translation that provides byteswapped access to + * physical memory on big-endian MIPS SoCs. + */ + BHND_DMA_TRANSLATION_PHYSMAP = (1<<0), + + /** + * Provides a byte-swapped mapping; write requests will be byte-swapped + * before being written to memory, and read requests will be + * byte-swapped before being returned. + * + * This is primarily used to perform efficient byte swapping of DMA + * data on embedded MIPS SoCs executing in big-endian mode. + */ + BHND_DMA_TRANSLATION_BYTESWAPPED = (1<<1), +}; + +/** * A bhnd(4) bus resource. * * This provides an abstract interface to per-core resources that may require * bus-level remapping of address windows prior to access. */ struct bhnd_resource { struct resource *res; /**< the system resource. */ bool direct; /**< false if the resource requires * bus window remapping before it * is MMIO accessible. */ }; /** Wrap the active resource @p _r in a bhnd_resource structure */ #define BHND_DIRECT_RESOURCE(_r) ((struct bhnd_resource) { \ .res = (_r), \ .direct = true, \ }) /** * Device quirk table descriptor. */ struct bhnd_device_quirk { struct bhnd_device_match desc; /**< device match descriptor */ uint32_t quirks; /**< quirk flags */ }; #define BHND_CORE_QUIRK(_rev, _flags) \ {{ BHND_MATCH_CORE_REV(_rev) }, (_flags) } #define BHND_CHIP_QUIRK(_chip, _rev, _flags) \ {{ BHND_MATCH_CHIP_IR(BCM ## _chip, _rev) }, (_flags) } #define BHND_PKG_QUIRK(_chip, _pkg, _flags) \ {{ BHND_MATCH_CHIP_IP(BCM ## _chip, BCM ## _chip ## _pkg) }, (_flags) } #define BHND_BOARD_QUIRK(_board, _flags) \ {{ BHND_MATCH_BOARD_TYPE(_board) }, \ (_flags) } #define BHND_DEVICE_QUIRK_END { { BHND_MATCH_ANY }, 0 } #define BHND_DEVICE_QUIRK_IS_END(_q) \ (((_q)->desc.m.match_flags == 0) && (_q)->quirks == 0) enum { BHND_DF_ANY = 0, BHND_DF_HOSTB = (1<<0), /**< core is serving as the bus' host * bridge. implies BHND_DF_ADAPTER */ BHND_DF_SOC = (1<<1), /**< core is attached to a native bus (BHND_ATTACH_NATIVE) */ BHND_DF_ADAPTER = (1<<2), /**< core is attached to a bridged * adapter (BHND_ATTACH_ADAPTER) */ }; /** Device probe table descriptor */ struct bhnd_device { const struct bhnd_device_match core; /**< core match descriptor */ const char *desc; /**< device description, or NULL. */ const struct bhnd_device_quirk *quirks_table; /**< quirks table for this device, or NULL */ uint32_t device_flags; /**< required BHND_DF_* flags */ }; #define _BHND_DEVICE(_vendor, _device, _desc, _quirks, \ _flags, ...) \ { { BHND_MATCH_CORE(BHND_MFGID_ ## _vendor, \ BHND_COREID_ ## _device) }, _desc, _quirks, \ _flags } #define BHND_DEVICE(_vendor, _device, _desc, _quirks, ...) \ _BHND_DEVICE(_vendor, _device, _desc, _quirks, \ ## __VA_ARGS__, 0) #define BHND_DEVICE_END { { BHND_MATCH_ANY }, NULL, NULL, 0 } #define BHND_DEVICE_IS_END(_d) \ (BHND_MATCH_IS_ANY(&(_d)->core) && (_d)->desc == NULL) /** * bhnd device sort order. */ typedef enum { BHND_DEVICE_ORDER_ATTACH, /**< sort by bhnd(4) device attach order; child devices should be probed/attached in this order */ BHND_DEVICE_ORDER_DETACH, /**< sort by bhnd(4) device detach order; child devices should be detached, suspended, and shutdown in this order */ } bhnd_device_order; /** * A registry of bhnd service providers. */ struct bhnd_service_registry { STAILQ_HEAD(,bhnd_service_entry) entries; /**< registered services */ struct mtx lock; /**< state lock */ }; /** * bhnd service provider flags. */ enum { BHND_SPF_INHERITED = (1<<0), /**< service provider reference was inherited from a parent bus, and should be deregistered when the last active reference is released */ }; const char *bhnd_vendor_name(uint16_t vendor); const char *bhnd_port_type_name(bhnd_port_type port_type); const char *bhnd_nvram_src_name(bhnd_nvram_src nvram_src); const char *bhnd_find_core_name(uint16_t vendor, uint16_t device); bhnd_devclass_t bhnd_find_core_class(uint16_t vendor, uint16_t device); const char *bhnd_core_name(const struct bhnd_core_info *ci); bhnd_devclass_t bhnd_core_class(const struct bhnd_core_info *ci); int bhnd_format_chip_id(char *buffer, size_t size, uint16_t chip_id); device_t bhnd_bus_match_child(device_t bus, const struct bhnd_core_match *desc); device_t bhnd_bus_find_child(device_t bus, bhnd_devclass_t class, int unit); int bhnd_bus_get_children(device_t bus, device_t **devlistp, int *devcountp, bhnd_device_order order); void bhnd_bus_free_children(device_t *devlist); int bhnd_bus_probe_children(device_t bus); int bhnd_sort_devices(device_t *devlist, size_t devcount, bhnd_device_order order); device_t bhnd_find_bridge_root(device_t dev, devclass_t bus_class); const struct bhnd_core_info *bhnd_match_core( const struct bhnd_core_info *cores, u_int num_cores, const struct bhnd_core_match *desc); const struct bhnd_core_info *bhnd_find_core( const struct bhnd_core_info *cores, u_int num_cores, bhnd_devclass_t class); struct bhnd_core_match bhnd_core_get_match_desc( const struct bhnd_core_info *core); bool bhnd_cores_equal( const struct bhnd_core_info *lhs, const struct bhnd_core_info *rhs); bool bhnd_core_matches( const struct bhnd_core_info *core, const struct bhnd_core_match *desc); bool bhnd_chip_matches( const struct bhnd_chipid *chipid, const struct bhnd_chip_match *desc); bool bhnd_board_matches( const struct bhnd_board_info *info, const struct bhnd_board_match *desc); bool bhnd_hwrev_matches(uint16_t hwrev, const struct bhnd_hwrev_match *desc); bool bhnd_device_matches(device_t dev, const struct bhnd_device_match *desc); const struct bhnd_device *bhnd_device_lookup(device_t dev, const struct bhnd_device *table, size_t entry_size); uint32_t bhnd_device_quirks(device_t dev, const struct bhnd_device *table, size_t entry_size); struct bhnd_core_info bhnd_get_core_info(device_t dev); int bhnd_alloc_resources(device_t dev, struct resource_spec *rs, struct bhnd_resource **res); void bhnd_release_resources(device_t dev, const struct resource_spec *rs, struct bhnd_resource **res); struct bhnd_chipid bhnd_parse_chipid(uint32_t idreg, bhnd_addr_t enum_addr); int bhnd_chipid_fixed_ncores( const struct bhnd_chipid *cid, uint16_t chipc_hwrev, uint8_t *ncores); int bhnd_read_chipid(device_t dev, struct resource_spec *rs, bus_size_t chipc_offset, struct bhnd_chipid *result); void bhnd_set_custom_core_desc(device_t dev, const char *name); void bhnd_set_default_core_desc(device_t dev); void bhnd_set_default_bus_desc(device_t dev, const struct bhnd_chipid *chip_id); int bhnd_nvram_getvar_str(device_t dev, const char *name, char *buf, size_t len, size_t *rlen); int bhnd_nvram_getvar_uint(device_t dev, const char *name, void *value, int width); int bhnd_nvram_getvar_uint8(device_t dev, const char *name, uint8_t *value); int bhnd_nvram_getvar_uint16(device_t dev, const char *name, uint16_t *value); int bhnd_nvram_getvar_uint32(device_t dev, const char *name, uint32_t *value); int bhnd_nvram_getvar_int(device_t dev, const char *name, void *value, int width); int bhnd_nvram_getvar_int8(device_t dev, const char *name, int8_t *value); int bhnd_nvram_getvar_int16(device_t dev, const char *name, int16_t *value); int bhnd_nvram_getvar_int32(device_t dev, const char *name, int32_t *value); int bhnd_nvram_getvar_array(device_t dev, const char *name, void *buf, size_t count, bhnd_nvram_type type); int bhnd_service_registry_init( struct bhnd_service_registry *bsr); int bhnd_service_registry_fini( struct bhnd_service_registry *bsr); int bhnd_service_registry_add( struct bhnd_service_registry *bsr, device_t provider, bhnd_service_t service, uint32_t flags); int bhnd_service_registry_remove( struct bhnd_service_registry *bsr, device_t provider, bhnd_service_t service); device_t bhnd_service_registry_retain( struct bhnd_service_registry *bsr, bhnd_service_t service); bool bhnd_service_registry_release( struct bhnd_service_registry *bsr, device_t provider, bhnd_service_t service); int bhnd_bus_generic_register_provider( device_t dev, device_t child, device_t provider, bhnd_service_t service); int bhnd_bus_generic_deregister_provider( device_t dev, device_t child, device_t provider, bhnd_service_t service); device_t bhnd_bus_generic_retain_provider(device_t dev, device_t child, bhnd_service_t service); void bhnd_bus_generic_release_provider(device_t dev, device_t child, device_t provider, bhnd_service_t service); int bhnd_bus_generic_sr_register_provider( device_t dev, device_t child, device_t provider, bhnd_service_t service); int bhnd_bus_generic_sr_deregister_provider( device_t dev, device_t child, device_t provider, bhnd_service_t service); device_t bhnd_bus_generic_sr_retain_provider(device_t dev, device_t child, bhnd_service_t service); void bhnd_bus_generic_sr_release_provider(device_t dev, device_t child, device_t provider, bhnd_service_t service); bool bhnd_bus_generic_is_hw_disabled(device_t dev, device_t child); bool bhnd_bus_generic_is_region_valid(device_t dev, device_t child, bhnd_port_type type, u_int port, u_int region); int bhnd_bus_generic_get_nvram_var(device_t dev, device_t child, const char *name, void *buf, size_t *size, bhnd_nvram_type type); const struct bhnd_chipid *bhnd_bus_generic_get_chipid(device_t dev, device_t child); +int bhnd_bus_generic_get_dma_translation( + device_t dev, device_t child, u_int width, + uint32_t flags, bus_dma_tag_t *dmat, + struct bhnd_dma_translation *translation); int bhnd_bus_generic_read_board_info(device_t dev, device_t child, struct bhnd_board_info *info); struct bhnd_resource *bhnd_bus_generic_alloc_resource (device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags); int bhnd_bus_generic_release_resource (device_t dev, device_t child, int type, int rid, struct bhnd_resource *r); int bhnd_bus_generic_activate_resource (device_t dev, device_t child, int type, int rid, struct bhnd_resource *r); int bhnd_bus_generic_deactivate_resource (device_t dev, device_t child, int type, int rid, struct bhnd_resource *r); 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 * * @param driver A bhnd bus driver instance. */ static inline bhnd_erom_class_t * bhnd_driver_get_erom_class(driver_t *driver) { return (BHND_BUS_GET_EROM_CLASS(driver)); } /** * Return the active host bridge core for the bhnd bus, if any, or NULL if * not found. * * @param dev A bhnd bus device. */ static inline device_t bhnd_bus_find_hostb_device(device_t dev) { return (BHND_BUS_FIND_HOSTB_DEVICE(dev)); } /** * Register a provider for a given @p service. * * @param dev The device to register as a service provider * with its parent bus. * @param service The service for which @p dev will be registered. * * @retval 0 success * @retval EEXIST if an entry for @p service already exists. * @retval non-zero if registering @p dev otherwise fails, a regular * unix error code will be returned. */ static inline int bhnd_register_provider(device_t dev, bhnd_service_t service) { return (BHND_BUS_REGISTER_PROVIDER(device_get_parent(dev), dev, dev, service)); } /** * Attempt to remove a service provider registration for @p dev. * * @param dev The device to be deregistered as a service provider. * @param service The service for which @p dev will be deregistered, or * BHND_SERVICE_INVALID to remove all service registrations * for @p dev. * * @retval 0 success * @retval EBUSY if active references to @p dev exist; @see * bhnd_retain_provider() and bhnd_release_provider(). */ static inline int bhnd_deregister_provider(device_t dev, bhnd_service_t service) { return (BHND_BUS_DEREGISTER_PROVIDER(device_get_parent(dev), dev, dev, service)); } /** * Retain and return a reference to the registered @p service provider, if any. * * @param dev The requesting device. * @param service The service for which a provider should be returned. * * On success, the caller assumes ownership the returned provider, and * is responsible for releasing this reference via * BHND_BUS_RELEASE_PROVIDER(). * * @retval device_t success * @retval NULL if no provider is registered for @p service. */ static inline device_t bhnd_retain_provider(device_t dev, bhnd_service_t service) { return (BHND_BUS_RETAIN_PROVIDER(device_get_parent(dev), dev, service)); } /** * Release a reference to a provider device previously returned by * bhnd_retain_provider(). * * @param dev The requesting device. * @param provider The provider to be released. * @param service The service for which @p provider was previously retained. */ static inline void bhnd_release_provider(device_t dev, device_t provider, bhnd_service_t service) { return (BHND_BUS_RELEASE_PROVIDER(device_get_parent(dev), dev, provider, service)); } /** * Return true if the hardware components required by @p dev are known to be * unpopulated or otherwise unusable. * * In some cases, enumerated devices may have pins that are left floating, or * the hardware may otherwise be non-functional; this method allows a parent * device to explicitly specify if a successfully enumerated @p dev should * be disabled. * * @param dev A bhnd bus child device. */ static inline bool bhnd_is_hw_disabled(device_t dev) { return (BHND_BUS_IS_HW_DISABLED(device_get_parent(dev), dev)); } /** * Return the BHND chip identification info for the bhnd bus. * * @param dev A bhnd bus child device. */ static inline const struct bhnd_chipid * bhnd_get_chipid(device_t dev) { return (BHND_BUS_GET_CHIPID(device_get_parent(dev), dev)); }; /** * Read the current value of a bhnd(4) device's per-core I/O control register. * * @param dev The bhnd bus child device to be queried. * @param[out] ioctl On success, the I/O control register value. * * @retval 0 success * @retval EINVAL If @p child is not a direct child of @p dev. * @retval ENODEV If agent/config space for @p child is unavailable. * @retval non-zero If reading the IOCTL register otherwise fails, a regular * unix error code will be returned. */ static inline int bhnd_read_ioctl(device_t dev, uint16_t *ioctl) { return (BHND_BUS_READ_IOCTL(device_get_parent(dev), dev, ioctl)); } /** * Write @p value and @p mask to a bhnd(4) device's per-core I/O control * register. * * @param dev The bhnd bus child device for which the IOCTL register will be * written. * @param value The value to be written (see BHND_IOCTL_*). * @param mask Only the bits defined by @p mask will be updated from @p value. * * @retval 0 success * @retval EINVAL If @p child is not a direct child of @p dev. * @retval ENODEV If agent/config space for @p child is unavailable. * @retval non-zero If writing the IOCTL register otherwise fails, a regular * unix error code will be returned. */ static inline int bhnd_write_ioctl(device_t dev, uint16_t value, uint16_t mask) { return (BHND_BUS_WRITE_IOCTL(device_get_parent(dev), dev, value, mask)); } /** * Read the current value of a bhnd(4) device's per-core I/O status register. * * @param dev The bhnd bus child device to be queried. * @param[out] iost On success, the I/O status register value. * * @retval 0 success * @retval EINVAL If @p child is not a direct child of @p dev. * @retval ENODEV If agent/config space for @p child is unavailable. * @retval non-zero If reading the IOST register otherwise fails, a regular * unix error code will be returned. */ static inline int bhnd_read_iost(device_t dev, uint16_t *iost) { return (BHND_BUS_READ_IOST(device_get_parent(dev), dev, iost)); } /** * Return true if the given bhnd device's hardware is currently held * in a RESET state or otherwise not clocked (BHND_IOCTL_CLK_EN). * * @param dev The device to query. * * @retval true If @p dev is held in RESET or not clocked (BHND_IOCTL_CLK_EN), * or an error occured determining @p dev's hardware state. * @retval false If @p dev is clocked and is not held in RESET. */ static inline bool bhnd_is_hw_suspended(device_t dev) { return (BHND_BUS_IS_HW_SUSPENDED(device_get_parent(dev), dev)); } /** * Place the bhnd(4) device's hardware into a reset state, and then bring the * hardware out of reset with BHND_IOCTL_CLK_EN and @p ioctl flags set. * * Any clock or resource PMU requests previously made by @p dev will be * invalidated. * * @param dev The device to be reset. * @param ioctl Device-specific core ioctl flags to be supplied on reset * (see BHND_IOCTL_*). * * @retval 0 success * @retval non-zero error */ static inline int bhnd_reset_hw(device_t dev, uint16_t ioctl) { return (BHND_BUS_RESET_HW(device_get_parent(dev), dev, ioctl)); } /** * Suspend @p child's hardware in a low-power reset state. * * Any clock or resource PMU requests previously made by @p dev will be * invalidated. * * The hardware may be brought out of reset via bhnd_reset_hw(). * * @param dev The device to be suspended. * * @retval 0 success * @retval non-zero error */ static inline int bhnd_suspend_hw(device_t dev) { return (BHND_BUS_SUSPEND_HW(device_get_parent(dev), dev)); } /** * If supported by the chipset, return the clock source for the given clock. * * This function is only supported on early PWRCTL-equipped chipsets * that expose clock management via their host bridge interface. Currently, * this includes PCI (not PCIe) devices, with ChipCommon core revisions 0-9. * * @param dev A bhnd bus child device. * @param clock The clock for which a clock source will be returned. * * @retval bhnd_clksrc The clock source for @p clock. * @retval BHND_CLKSRC_UNKNOWN If @p clock is unsupported, or its * clock source is not known to the bus. */ static inline bhnd_clksrc bhnd_pwrctl_get_clksrc(device_t dev, bhnd_clock clock) { return (BHND_BUS_PWRCTL_GET_CLKSRC(device_get_parent(dev), dev, clock)); } /** * If supported by the chipset, gate @p clock * * This function is only supported on early PWRCTL-equipped chipsets * that expose clock management via their host bridge interface. Currently, * this includes PCI (not PCIe) devices, with ChipCommon core revisions 0-9. * * @param dev A bhnd bus child device. * @param clock The clock to be disabled. * * @retval 0 success * @retval ENODEV If bus-level clock source management is not supported. * @retval ENXIO If bus-level management of @p clock is not supported. */ static inline int bhnd_pwrctl_gate_clock(device_t dev, bhnd_clock clock) { return (BHND_BUS_PWRCTL_GATE_CLOCK(device_get_parent(dev), dev, clock)); } /** * If supported by the chipset, ungate @p clock * * This function is only supported on early PWRCTL-equipped chipsets * that expose clock management via their host bridge interface. Currently, * this includes PCI (not PCIe) devices, with ChipCommon core revisions 0-9. * * @param dev A bhnd bus child device. * @param clock The clock to be enabled. * * @retval 0 success * @retval ENODEV If bus-level clock source management is not supported. * @retval ENXIO If bus-level management of @p clock is not supported. */ static inline int bhnd_pwrctl_ungate_clock(device_t dev, bhnd_clock clock) { return (BHND_BUS_PWRCTL_UNGATE_CLOCK(device_get_parent(dev), dev, clock)); } /** * Return the BHND attachment type of the parent bhnd bus. * * @param dev A bhnd bus child device. * * @retval BHND_ATTACH_ADAPTER if the bus is resident on a bridged adapter, * such as a WiFi chipset. * @retval BHND_ATTACH_NATIVE if the bus provides hardware services (clock, * CPU, etc) to a directly attached native host. */ static inline bhnd_attach_type bhnd_get_attach_type (device_t dev) { return (BHND_BUS_GET_ATTACH_TYPE(device_get_parent(dev), dev)); +} + +/** + * Find the best available DMA address translation capable of mapping a + * physical host address to a BHND DMA device address of @p width with + * @p flags. + * + * @param dev A bhnd bus child device. + * @param width The address width within which the translation window must + * reside (see BHND_DMA_ADDR_*). + * @param flags Required translation flags (see BHND_DMA_TRANSLATION_*). + * @param[out] dmat On success, will be populated with a DMA tag specifying the + * @p translation DMA address restrictions. This argment may be NULL if the DMA + * tag is not desired. + * the set of valid host DMA addresses reachable via @p translation. + * @param[out] translation On success, will be populated with a DMA address + * translation descriptor for @p child. This argment may be NULL if the + * descriptor is not desired. + * + * @retval 0 success + * @retval ENODEV If DMA is not supported. + * @retval ENOENT If no DMA translation matching @p width and @p flags is + * available. + * @retval non-zero If determining the DMA address translation for @p child + * otherwise fails, a regular unix error code will be returned. + */ +static inline int +bhnd_get_dma_translation(device_t dev, u_int width, uint32_t flags, + bus_dma_tag_t *dmat, struct bhnd_dma_translation *translation) +{ + return (BHND_BUS_GET_DMA_TRANSLATION(device_get_parent(dev), dev, width, + flags, dmat, translation)); } /** * Attempt to read the BHND board identification from the bhnd bus. * * This relies on NVRAM access, and will fail if a valid NVRAM device cannot * be found, or is not yet attached. * * @param dev The parent of @p child. * @param child The bhnd device requesting board info. * @param[out] info On success, will be populated with the bhnd(4) device's * board information. * * @retval 0 success * @retval ENODEV No valid NVRAM source could be found. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ static inline int bhnd_read_board_info(device_t dev, struct bhnd_board_info *info) { return (BHND_BUS_READ_BOARD_INFO(device_get_parent(dev), dev, info)); } /** * Return the number of interrupt lines assigned to @p dev. * * @param dev A bhnd bus child device. */ static inline u_int bhnd_get_intr_count(device_t dev) { return (BHND_BUS_GET_INTR_COUNT(device_get_parent(dev), dev)); } /** * Get the backplane interrupt vector of the @p intr line attached to @p dev. * * @param dev A bhnd bus 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. */ static inline int bhnd_get_intr_ivec(device_t dev, u_int intr, u_int *ivec) { 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. * * The region containing the core's PMU register block (if any) must be * allocated via bus_alloc_resource(9) (or bhnd_alloc_resource) before * calling bhnd_alloc_pmu(), and must not be released until after * calling bhnd_release_pmu(). * * @param dev The parent of @p child. * @param child The requesting bhnd device. * * @retval 0 success * @retval non-zero If allocating PMU request state otherwise fails, a * regular unix error code will be returned. */ static inline int bhnd_alloc_pmu(device_t dev) { return (BHND_BUS_ALLOC_PMU(device_get_parent(dev), dev)); } /** * Release any per-core PMU resources allocated for @p child. Any outstanding * PMU requests are are discarded. * * @param dev The parent of @p child. * @param child The requesting bhnd device. * * @retval 0 success * @retval non-zero If releasing PMU request state otherwise fails, a * regular unix error code will be returned, and * the core state will be left unmodified. */ static inline int bhnd_release_pmu(device_t dev) { return (BHND_BUS_RELEASE_PMU(device_get_parent(dev), dev)); } /** * Request that @p clock (or faster) be routed to @p dev. * * @note A driver must ask the bhnd bus to allocate clock request state * via bhnd_alloc_pmu() before it can request clock resources. * * @note Any outstanding PMU clock requests will be discarded upon calling * BHND_BUS_RESET_HW() or BHND_BUS_SUSPEND_HW(). * * @param dev The bhnd(4) device to which @p clock should be routed. * @param clock The requested clock source. * * @retval 0 success * @retval ENODEV If an unsupported clock was requested. * @retval ENXIO If the PMU has not been initialized or is otherwise unvailable, */ static inline int bhnd_request_clock(device_t dev, bhnd_clock clock) { return (BHND_BUS_REQUEST_CLOCK(device_get_parent(dev), dev, clock)); } /** * Request that @p clocks be powered on behalf of @p dev. * * This will power any clock sources (e.g. XTAL, PLL, etc) required for * @p clocks and wait until they are ready, discarding any previous * requests by @p dev. * * @note A driver must ask the bhnd bus to allocate clock request state * via bhnd_alloc_pmu() before it can request clock resources. * * @note Any outstanding PMU clock requests will be discarded upon calling * BHND_BUS_RESET_HW() or BHND_BUS_SUSPEND_HW(). * * @param dev The requesting bhnd(4) device. * @param clocks The clock(s) to be enabled. * * @retval 0 success * @retval ENODEV If an unsupported clock was requested. * @retval ENXIO If the PMU has not been initialized or is otherwise unvailable. */ static inline int bhnd_enable_clocks(device_t dev, uint32_t clocks) { return (BHND_BUS_ENABLE_CLOCKS(device_get_parent(dev), dev, clocks)); } /** * Power up an external PMU-managed resource assigned to @p dev. * * @note A driver must ask the bhnd bus to allocate PMU request state * via bhnd_alloc_pmu() before it can request PMU resources. * * @note Any outstanding PMU resource requests will be released upon calling * bhnd_reset_hw() or bhnd_suspend_hw(). * * @param dev The requesting bhnd(4) device. * @param rsrc The core-specific external resource identifier. * * @retval 0 success * @retval ENODEV If the PMU does not support @p rsrc. * @retval ENXIO If the PMU has not been initialized or is otherwise unvailable. */ static inline int bhnd_request_ext_rsrc(device_t dev, u_int rsrc) { return (BHND_BUS_REQUEST_EXT_RSRC(device_get_parent(dev), dev, rsrc)); } /** * Power down an external PMU-managed resource assigned to @p dev. * * A driver must ask the bhnd bus to allocate PMU request state * via bhnd_alloc_pmu() before it can request PMU resources. * * @param dev The requesting bhnd(4) device. * @param rsrc The core-specific external resource identifier. * * @retval 0 success * @retval ENODEV If the PMU does not support @p rsrc. * @retval ENXIO If the PMU has not been initialized or is otherwise unvailable. */ static inline int bhnd_release_ext_rsrc(device_t dev, u_int rsrc) { return (BHND_BUS_RELEASE_EXT_RSRC(device_get_parent(dev), dev, rsrc)); } /** * Read @p width bytes at @p offset from the bus-specific agent/config * space of @p dev. * * @param dev The bhnd device for which @p offset should be read. * @param offset The offset to be read. * @param[out] value On success, the will be set to the @p width value read * at @p offset. * @param width The size of the access. Must be 1, 2 or 4 bytes. * * The exact behavior of this method is bus-specific. In the case of * bcma(4), this method provides access to the first agent port of @p child. * * @note Device drivers should only use this API for functionality * that is not available via another bhnd(4) function. * * @retval 0 success * @retval EINVAL If @p child is not a direct child of @p dev. * @retval EINVAL If @p width is not one of 1, 2, or 4 bytes. * @retval ENODEV If accessing agent/config space for @p child is unsupported. * @retval EFAULT If reading @p width at @p offset exceeds the bounds of * the mapped agent/config space for @p child. */ static inline uint32_t bhnd_read_config(device_t dev, bus_size_t offset, void *value, u_int width) { return (BHND_BUS_READ_CONFIG(device_get_parent(dev), dev, offset, value, width)); } /** * Write @p width bytes at @p offset to the bus-specific agent/config * space of @p dev. * * @param dev The bhnd device for which @p offset should be read. * @param offset The offset to be written. * @param value A pointer to the value to be written. * @param width The size of @p value. Must be 1, 2 or 4 bytes. * * The exact behavior of this method is bus-specific. In the case of * bcma(4), this method provides access to the first agent port of @p child. * * @note Device drivers should only use this API for functionality * that is not available via another bhnd(4) function. * * @retval 0 success * @retval EINVAL If @p child is not a direct child of @p dev. * @retval EINVAL If @p width is not one of 1, 2, or 4 bytes. * @retval ENODEV If accessing agent/config space for @p child is unsupported. * @retval EFAULT If reading @p width at @p offset exceeds the bounds of * the mapped agent/config space for @p child. */ static inline int bhnd_write_config(device_t dev, bus_size_t offset, const void *value, u_int width) { return (BHND_BUS_WRITE_CONFIG(device_get_parent(dev), dev, offset, value, width)); } /** * Read an NVRAM variable, coerced to the requested @p type. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] buf A buffer large enough to hold @p len bytes. On * success, the requested value will be written to * this buffer. This argment may be NULL if * the value is not desired. * @param[in,out] len The maximum capacity of @p buf. On success, * will be set to the actual size of the requested * value. * @param type The desired data representation to be written * to @p buf. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval ENOMEM If a buffer of @p size is too small to hold the * requested value. * @retval EOPNOTSUPP If the value cannot be coerced to @p type. * @retval ERANGE If value coercion would overflow @p type. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ static inline int bhnd_nvram_getvar(device_t dev, const char *name, void *buf, size_t *len, bhnd_nvram_type type) { return (BHND_BUS_GET_NVRAM_VAR(device_get_parent(dev), dev, name, buf, len, type)); } /** * Allocate a resource from a device's parent bhnd(4) bus. * * @param dev The device requesting resource ownership. * @param type The type of resource to allocate. This may be any type supported * by the standard bus APIs. * @param rid The bus-specific handle identifying the resource being allocated. * @param start The start address of the resource. * @param end The end address of the resource. * @param count The size of the resource. * @param flags The flags for the resource to be allocated. These may be any * values supported by the standard bus APIs. * * To request the resource's default addresses, pass @p start and * @p end values of @c 0 and @c ~0, respectively, and * a @p count of @c 1. * * @retval NULL The resource could not be allocated. * @retval resource The allocated resource. */ static inline struct bhnd_resource * bhnd_alloc_resource(device_t dev, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { return BHND_BUS_ALLOC_RESOURCE(device_get_parent(dev), dev, type, rid, start, end, count, flags); } /** * Allocate a resource from a device's parent bhnd(4) bus, using the * resource's default start, end, and count values. * * @param dev The device requesting resource ownership. * @param type The type of resource to allocate. This may be any type supported * by the standard bus APIs. * @param rid The bus-specific handle identifying the resource being allocated. * @param flags The flags for the resource to be allocated. These may be any * values supported by the standard bus APIs. * * @retval NULL The resource could not be allocated. * @retval resource The allocated resource. */ static inline struct bhnd_resource * bhnd_alloc_resource_any(device_t dev, int type, int *rid, u_int flags) { return bhnd_alloc_resource(dev, type, rid, 0, ~0, 1, flags); } /** * Activate a previously allocated bhnd resource. * * @param dev The device holding ownership of the allocated resource. * @param type The type of the resource. * @param rid The bus-specific handle identifying the resource. * @param r A pointer to the resource returned by bhnd_alloc_resource or * BHND_BUS_ALLOC_RESOURCE. * * @retval 0 success * @retval non-zero an error occurred while activating the resource. */ static inline int bhnd_activate_resource(device_t dev, int type, int rid, struct bhnd_resource *r) { return BHND_BUS_ACTIVATE_RESOURCE(device_get_parent(dev), dev, type, rid, r); } /** * Deactivate a previously activated bhnd resource. * * @param dev The device holding ownership of the activated resource. * @param type The type of the resource. * @param rid The bus-specific handle identifying the resource. * @param r A pointer to the resource returned by bhnd_alloc_resource or * BHND_BUS_ALLOC_RESOURCE. * * @retval 0 success * @retval non-zero an error occurred while activating the resource. */ static inline int bhnd_deactivate_resource(device_t dev, int type, int rid, struct bhnd_resource *r) { return BHND_BUS_DEACTIVATE_RESOURCE(device_get_parent(dev), dev, type, rid, r); } /** * Free a resource allocated by bhnd_alloc_resource(). * * @param dev The device holding ownership of the resource. * @param type The type of the resource. * @param rid The bus-specific handle identifying the resource. * @param r A pointer to the resource returned by bhnd_alloc_resource or * BHND_ALLOC_RESOURCE. * * @retval 0 success * @retval non-zero an error occurred while activating the resource. */ static inline int bhnd_release_resource(device_t dev, int type, int rid, struct bhnd_resource *r) { return BHND_BUS_RELEASE_RESOURCE(device_get_parent(dev), dev, type, rid, r); } /** * Return true if @p region_num is a valid region on @p port_num of * @p type attached to @p dev. * * @param dev A bhnd bus child device. * @param type The port type being queried. * @param port_num The port number being queried. * @param region_num The region number being queried. */ static inline bool bhnd_is_region_valid(device_t dev, bhnd_port_type type, u_int port_num, u_int region_num) { return (BHND_BUS_IS_REGION_VALID(device_get_parent(dev), dev, type, port_num, region_num)); } /** * Return the number of ports of type @p type attached to @p def. * * @param dev A bhnd bus child device. * @param type The port type being queried. */ static inline u_int bhnd_get_port_count(device_t dev, bhnd_port_type type) { return (BHND_BUS_GET_PORT_COUNT(device_get_parent(dev), dev, type)); } /** * Return the number of memory regions mapped to @p child @p port of * type @p type. * * @param dev A bhnd bus child device. * @param port The port number being queried. * @param type The port type being queried. */ static inline u_int bhnd_get_region_count(device_t dev, bhnd_port_type type, u_int port) { return (BHND_BUS_GET_REGION_COUNT(device_get_parent(dev), dev, type, port)); } /** * Return the resource-ID for a memory region on the given device port. * * @param dev A bhnd bus child device. * @param type The port type. * @param port The port identifier. * @param region The identifier of the memory region on @p port. * * @retval int The RID for the given @p port and @p region on @p device. * @retval -1 No such port/region found. */ static inline int bhnd_get_port_rid(device_t dev, bhnd_port_type type, u_int port, u_int region) { return BHND_BUS_GET_PORT_RID(device_get_parent(dev), dev, type, port, region); } /** * Decode a port / region pair on @p dev defined by @p rid. * * @param dev A bhnd bus child device. * @param type The resource type. * @param rid The resource identifier. * @param[out] port_type The decoded port type. * @param[out] port The decoded port identifier. * @param[out] region The decoded region identifier. * * @retval 0 success * @retval non-zero No matching port/region found. */ static inline int bhnd_decode_port_rid(device_t dev, int type, int rid, bhnd_port_type *port_type, u_int *port, u_int *region) { return BHND_BUS_DECODE_PORT_RID(device_get_parent(dev), dev, type, rid, port_type, port, region); } /** * Get the address and size of @p region on @p port. * * @param dev A bhnd bus child device. * @param port_type The port type. * @param port The port identifier. * @param region The identifier of the memory region on @p port. * @param[out] region_addr The region's base address. * @param[out] region_size The region's size. * * @retval 0 success * @retval non-zero No matching port/region found. */ static inline int bhnd_get_region_addr(device_t dev, bhnd_port_type port_type, u_int port, u_int region, bhnd_addr_t *region_addr, bhnd_size_t *region_size) { return BHND_BUS_GET_REGION_ADDR(device_get_parent(dev), dev, port_type, port, region, region_addr, region_size); } /* * bhnd bus-level equivalents of the bus_(read|write|set|barrier|...) * macros (compatible with bhnd_resource). * * Generated with bhnd/tools/bus_macro.sh */ #define bhnd_bus_barrier(r, o, l, f) \ ((r)->direct) ? \ bus_barrier((r)->res, (o), (l), (f)) : \ BHND_BUS_BARRIER( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (l), (f)) #define bhnd_bus_read_1(r, o) \ ((r)->direct) ? \ bus_read_1((r)->res, (o)) : \ BHND_BUS_READ_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o)) #define bhnd_bus_read_multi_1(r, o, d, c) \ ((r)->direct) ? \ bus_read_multi_1((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_MULTI_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_read_region_1(r, o, d, c) \ ((r)->direct) ? \ bus_read_region_1((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_REGION_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_1(r, o, v) \ ((r)->direct) ? \ bus_write_1((r)->res, (o), (v)) : \ BHND_BUS_WRITE_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v)) #define bhnd_bus_write_multi_1(r, o, d, c) \ ((r)->direct) ? \ bus_write_multi_1((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_MULTI_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_region_1(r, o, d, c) \ ((r)->direct) ? \ bus_write_region_1((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_REGION_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_read_stream_1(r, o) \ ((r)->direct) ? \ bus_read_stream_1((r)->res, (o)) : \ BHND_BUS_READ_STREAM_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o)) #define bhnd_bus_read_multi_stream_1(r, o, d, c) \ ((r)->direct) ? \ bus_read_multi_stream_1((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_MULTI_STREAM_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_read_region_stream_1(r, o, d, c) \ ((r)->direct) ? \ bus_read_region_stream_1((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_REGION_STREAM_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_stream_1(r, o, v) \ ((r)->direct) ? \ bus_write_stream_1((r)->res, (o), (v)) : \ BHND_BUS_WRITE_STREAM_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v)) #define bhnd_bus_write_multi_stream_1(r, o, d, c) \ ((r)->direct) ? \ bus_write_multi_stream_1((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_MULTI_STREAM_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_region_stream_1(r, o, d, c) \ ((r)->direct) ? \ bus_write_region_stream_1((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_REGION_STREAM_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_set_multi_1(r, o, v, c) \ ((r)->direct) ? \ bus_set_multi_1((r)->res, (o), (v), (c)) : \ BHND_BUS_SET_MULTI_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v), (c)) #define bhnd_bus_set_region_1(r, o, v, c) \ ((r)->direct) ? \ bus_set_region_1((r)->res, (o), (v), (c)) : \ BHND_BUS_SET_REGION_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v), (c)) #define bhnd_bus_read_2(r, o) \ ((r)->direct) ? \ bus_read_2((r)->res, (o)) : \ BHND_BUS_READ_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o)) #define bhnd_bus_read_multi_2(r, o, d, c) \ ((r)->direct) ? \ bus_read_multi_2((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_MULTI_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_read_region_2(r, o, d, c) \ ((r)->direct) ? \ bus_read_region_2((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_REGION_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_2(r, o, v) \ ((r)->direct) ? \ bus_write_2((r)->res, (o), (v)) : \ BHND_BUS_WRITE_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v)) #define bhnd_bus_write_multi_2(r, o, d, c) \ ((r)->direct) ? \ bus_write_multi_2((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_MULTI_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_region_2(r, o, d, c) \ ((r)->direct) ? \ bus_write_region_2((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_REGION_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_read_stream_2(r, o) \ ((r)->direct) ? \ bus_read_stream_2((r)->res, (o)) : \ BHND_BUS_READ_STREAM_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o)) #define bhnd_bus_read_multi_stream_2(r, o, d, c) \ ((r)->direct) ? \ bus_read_multi_stream_2((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_MULTI_STREAM_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_read_region_stream_2(r, o, d, c) \ ((r)->direct) ? \ bus_read_region_stream_2((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_REGION_STREAM_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_stream_2(r, o, v) \ ((r)->direct) ? \ bus_write_stream_2((r)->res, (o), (v)) : \ BHND_BUS_WRITE_STREAM_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v)) #define bhnd_bus_write_multi_stream_2(r, o, d, c) \ ((r)->direct) ? \ bus_write_multi_stream_2((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_MULTI_STREAM_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_region_stream_2(r, o, d, c) \ ((r)->direct) ? \ bus_write_region_stream_2((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_REGION_STREAM_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_set_multi_2(r, o, v, c) \ ((r)->direct) ? \ bus_set_multi_2((r)->res, (o), (v), (c)) : \ BHND_BUS_SET_MULTI_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v), (c)) #define bhnd_bus_set_region_2(r, o, v, c) \ ((r)->direct) ? \ bus_set_region_2((r)->res, (o), (v), (c)) : \ BHND_BUS_SET_REGION_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v), (c)) #define bhnd_bus_read_4(r, o) \ ((r)->direct) ? \ bus_read_4((r)->res, (o)) : \ BHND_BUS_READ_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o)) #define bhnd_bus_read_multi_4(r, o, d, c) \ ((r)->direct) ? \ bus_read_multi_4((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_MULTI_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_read_region_4(r, o, d, c) \ ((r)->direct) ? \ bus_read_region_4((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_REGION_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_4(r, o, v) \ ((r)->direct) ? \ bus_write_4((r)->res, (o), (v)) : \ BHND_BUS_WRITE_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v)) #define bhnd_bus_write_multi_4(r, o, d, c) \ ((r)->direct) ? \ bus_write_multi_4((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_MULTI_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_region_4(r, o, d, c) \ ((r)->direct) ? \ bus_write_region_4((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_REGION_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_read_stream_4(r, o) \ ((r)->direct) ? \ bus_read_stream_4((r)->res, (o)) : \ BHND_BUS_READ_STREAM_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o)) #define bhnd_bus_read_multi_stream_4(r, o, d, c) \ ((r)->direct) ? \ bus_read_multi_stream_4((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_MULTI_STREAM_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_read_region_stream_4(r, o, d, c) \ ((r)->direct) ? \ bus_read_region_stream_4((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_REGION_STREAM_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_stream_4(r, o, v) \ ((r)->direct) ? \ bus_write_stream_4((r)->res, (o), (v)) : \ BHND_BUS_WRITE_STREAM_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v)) #define bhnd_bus_write_multi_stream_4(r, o, d, c) \ ((r)->direct) ? \ bus_write_multi_stream_4((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_MULTI_STREAM_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_region_stream_4(r, o, d, c) \ ((r)->direct) ? \ bus_write_region_stream_4((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_REGION_STREAM_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_set_multi_4(r, o, v, c) \ ((r)->direct) ? \ bus_set_multi_4((r)->res, (o), (v), (c)) : \ BHND_BUS_SET_MULTI_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v), (c)) #define bhnd_bus_set_region_4(r, o, v, c) \ ((r)->direct) ? \ bus_set_region_4((r)->res, (o), (v), (c)) : \ BHND_BUS_SET_REGION_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v), (c)) #endif /* _BHND_BHND_H_ */ Index: head/sys/dev/bhnd/bhnd_bus_if.m =================================================================== --- head/sys/dev/bhnd/bhnd_bus_if.m (revision 326079) +++ head/sys/dev/bhnd/bhnd_bus_if.m (revision 326080) @@ -1,1617 +1,1653 @@ #- # Copyright (c) 2015-2016 Landon Fuller # 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: # 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 ``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. # # $FreeBSD$ #include #include #include #include #include INTERFACE bhnd_bus; # # bhnd(4) bus interface # HEADER { /* forward declarations */ struct bhnd_board_info; struct bhnd_core_info; struct bhnd_chipid; + struct bhnd_dma_translation; struct bhnd_devinfo; struct bhnd_resource; } CODE { #include #include static bhnd_erom_class_t * bhnd_bus_null_get_erom_class(driver_t *driver) { return (NULL); } static struct bhnd_chipid * bhnd_bus_null_get_chipid(device_t dev, device_t child) { panic("bhnd_bus_get_chipid unimplemented"); } static int bhnd_bus_null_read_ioctl(device_t dev, device_t child, uint16_t *ioctl) { panic("bhnd_bus_read_ioctl unimplemented"); } static int bhnd_bus_null_write_ioctl(device_t dev, device_t child, uint16_t value, uint16_t mask) { panic("bhnd_bus_write_ioctl unimplemented"); } static int bhnd_bus_null_read_iost(device_t dev, device_t child, uint16_t *iost) { panic("bhnd_bus_read_iost unimplemented"); } static bool bhnd_bus_null_is_hw_suspended(device_t dev, device_t child) { panic("bhnd_bus_is_hw_suspended unimplemented"); } static int bhnd_bus_null_reset_hw(device_t dev, device_t child, uint16_t ioctl) { panic("bhnd_bus_reset_hw unimplemented"); } static int bhnd_bus_null_suspend_hw(device_t dev, device_t child) { panic("bhnd_bus_suspend_hw unimplemented"); } static bhnd_attach_type bhnd_bus_null_get_attach_type(device_t dev, device_t child) { panic("bhnd_bus_get_attach_type unimplemented"); } - + static bhnd_clksrc bhnd_bus_null_pwrctl_get_clksrc(device_t dev, device_t child, bhnd_clock clock) { return (BHND_CLKSRC_UNKNOWN); } static int bhnd_bus_null_pwrctl_gate_clock(device_t dev, device_t child, bhnd_clock clock) { return (ENODEV); } static int bhnd_bus_null_pwrctl_ungate_clock(device_t dev, device_t child, bhnd_clock clock) { return (ENODEV); } static int bhnd_bus_null_read_board_info(device_t dev, device_t child, struct bhnd_board_info *info) { panic("bhnd_bus_read_boardinfo unimplemented"); } static void bhnd_bus_null_child_added(device_t dev, device_t child) { } static int bhnd_bus_null_alloc_pmu(device_t dev, device_t child) { panic("bhnd_bus_alloc_pmu unimplemented"); } static int bhnd_bus_null_release_pmu(device_t dev, device_t child) { panic("bhnd_bus_release_pmu unimplemented"); } static int bhnd_bus_null_request_clock(device_t dev, device_t child, bhnd_clock clock) { panic("bhnd_bus_request_clock unimplemented"); } static int bhnd_bus_null_enable_clocks(device_t dev, device_t child, uint32_t clocks) { panic("bhnd_bus_enable_clocks unimplemented"); } static int bhnd_bus_null_request_ext_rsrc(device_t dev, device_t child, u_int rsrc) { panic("bhnd_bus_request_ext_rsrc unimplemented"); } static int bhnd_bus_null_release_ext_rsrc(device_t dev, device_t child, u_int rsrc) { panic("bhnd_bus_release_ext_rsrc unimplemented"); } static int bhnd_bus_null_read_config(device_t dev, device_t child, bus_size_t offset, void *value, u_int width) { panic("bhnd_bus_null_read_config unimplemented"); } static void bhnd_bus_null_write_config(device_t dev, device_t child, bus_size_t offset, void *value, u_int width) { panic("bhnd_bus_null_write_config unimplemented"); } static device_t bhnd_bus_null_find_hostb_device(device_t dev) { return (NULL); } static struct bhnd_service_registry * bhnd_bus_null_get_service_registry(device_t dev) { panic("bhnd_bus_get_service_registry unimplemented"); } static bool bhnd_bus_null_is_hw_disabled(device_t dev, device_t child) { panic("bhnd_bus_is_hw_disabled unimplemented"); } static int bhnd_bus_null_get_probe_order(device_t dev, device_t child) { 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) { return (-1); } static int bhnd_bus_null_decode_port_rid(device_t dev, device_t child, int type, int rid, bhnd_port_type *port_type, u_int *port, u_int *region) { return (ENOENT); } static int bhnd_bus_null_get_region_addr(device_t dev, device_t child, bhnd_port_type type, u_int port, u_int region, bhnd_addr_t *addr, bhnd_size_t *size) { return (ENOENT); } static int bhnd_bus_null_get_nvram_var(device_t dev, device_t child, const char *name, void *buf, size_t *size, bhnd_nvram_type type) { return (ENODEV); } } /** * Return the bhnd(4) bus driver's device enumeration parser class. * * @param driver The bhnd bus driver instance. */ STATICMETHOD bhnd_erom_class_t * get_erom_class { driver_t *driver; } DEFAULT bhnd_bus_null_get_erom_class; /** * Register a shared bus @p provider for a given @p service. * * @param dev The parent of @p child. * @param child The requesting child device. * @param provider The service provider to register. * @param service The service for which @p provider will be registered. * * @retval 0 success * @retval EEXIST if an entry for @p service already exists. * @retval non-zero if registering @p provider otherwise fails, a regular * unix error code will be returned. */ METHOD int register_provider { device_t dev; device_t child; device_t provider; bhnd_service_t service; } DEFAULT bhnd_bus_generic_register_provider; /** * Attempt to remove the @p service provider registration for @p provider. * * @param dev The parent of @p child. * @param child The requesting child device. * @param provider The service provider to be deregistered. * @param service The service for which @p provider will be deregistered, * or BHND_SERVICE_INVALID to remove all service * registrations for @p provider. * * @retval 0 success * @retval EBUSY if active references to @p provider exist; @see * BHND_BUS_RETAIN_PROVIDER() and * BHND_BUS_RELEASE_PROVIDER(). */ METHOD int deregister_provider { device_t dev; device_t child; device_t provider; bhnd_service_t service; } DEFAULT bhnd_bus_generic_deregister_provider; /** * Retain and return a reference to the registered @p service provider, if any. * * @param dev The parent of @p child. * @param child The requesting child device. * @param service The service for which a provider should be returned. * * On success, the caller assumes ownership the returned provider, and * is responsible for releasing this reference via * BHND_BUS_RELEASE_PROVIDER(). * * @retval device_t success * @retval NULL if no provider is registered for @p service. */ METHOD device_t retain_provider { device_t dev; device_t child; bhnd_service_t service; } DEFAULT bhnd_bus_generic_retain_provider; /** * Release a reference to a service provider previously returned by * BHND_BUS_RETAIN_PROVIDER(). * * @param dev The parent of @p child. * @param child The requesting child device. * @param provider The provider to be released. * @param service The service for which @p provider was previously * retained. */ METHOD void release_provider { device_t dev; device_t child; device_t provider; bhnd_service_t service; } DEFAULT bhnd_bus_generic_release_provider; /** * Return a struct bhnd_service_registry. * * Used by drivers which use bhnd_bus_generic_sr_register_provider() etc. * to implement service provider registration. It should return a service * registry that may be used to resolve provider requests from @p child. * * @param dev The parent of @p child. * @param child The requesting child device. */ METHOD struct bhnd_service_registry * get_service_registry { device_t dev; device_t child; } DEFAULT bhnd_bus_null_get_service_registry; /** * Return the active host bridge core for the bhnd bus, if any. * * @param dev The bhnd bus device. * * @retval device_t if a hostb device exists * @retval NULL if no hostb device is found. */ METHOD device_t find_hostb_device { device_t dev; } DEFAULT bhnd_bus_null_find_hostb_device; /** * Return true if the hardware components required by @p child are unpopulated * or otherwise unusable. * * In some cases, enumerated devices may have pins that are left floating, or * the hardware may otherwise be non-functional; this method allows a parent * device to explicitly specify if a successfully enumerated @p child should * be disabled. * * @param dev The device whose child is being examined. * @param child The child device. */ METHOD bool is_hw_disabled { device_t dev; device_t child; } DEFAULT bhnd_bus_null_is_hw_disabled; /** * Return the probe (and attach) order for @p child. * * All devices on the bhnd(4) bus will be probed, attached, or resumed in * ascending order; they will be suspended, shutdown, and detached in * descending order. * * The following device methods will be dispatched in ascending probe order * by the bus: * * - DEVICE_PROBE() * - DEVICE_ATTACH() * - DEVICE_RESUME() * * The following device methods will be dispatched in descending probe order * by the bus: * * - DEVICE_SHUTDOWN() * - DEVICE_DETACH() * - DEVICE_SUSPEND() * * @param dev The device whose child is being examined. * @param child The child device. * * Refer to BHND_PROBE_* and BHND_PROBE_ORDER_* for the standard set of * priorities. */ METHOD int get_probe_order { device_t dev; device_t child; } DEFAULT bhnd_bus_null_get_probe_order; /** * Return the BHND chip identification for the parent bus. * * @param dev The device whose child is being examined. * @param child The child device. */ METHOD const struct bhnd_chipid * get_chipid { device_t dev; device_t child; } DEFAULT bhnd_bus_null_get_chipid; /** * Return the BHND attachment type of the parent bus. * * @param dev The device whose child is being examined. * @param child The child device. * * @retval BHND_ATTACH_ADAPTER if the bus is resident on a bridged adapter, * such as a WiFi chipset. * @retval BHND_ATTACH_NATIVE if the bus provides hardware services (clock, * CPU, etc) to a directly attached native host. */ METHOD bhnd_attach_type get_attach_type { device_t dev; device_t child; } DEFAULT bhnd_bus_null_get_attach_type; + + +/** + * Find the best available DMA address translation capable of mapping a + * physical host address to a BHND DMA device address of @p width with + * @p flags. + * + * @param dev The parent of @p child. + * @param child The bhnd device requesting the DMA address translation. + * @param width The address width within which the translation window must + * reside (see BHND_DMA_ADDR_*). + * @param flags Required translation flags (see BHND_DMA_TRANSLATION_*). + * @param[out] dmat On success, will be populated with a DMA tag specifying the + * @p translation DMA address restrictions. This argment may be NULL if the DMA + * tag is not desired. + * the set of valid host DMA addresses reachable via @p translation. + * @param[out] translation On success, will be populated with a DMA address + * translation descriptor for @p child. This argment may be NULL if the + * descriptor is not desired. + * + * @retval 0 success + * @retval ENODEV If DMA is not supported. + * @retval ENOENT If no DMA translation matching @p width and @p flags is + * available. + * @retval non-zero If determining the DMA address translation for @p child + * otherwise fails, a regular unix error code will be returned. + */ +METHOD int get_dma_translation { + device_t dev; + device_t child; + u_int width; + uint32_t flags; + bus_dma_tag_t *dmat; + struct bhnd_dma_translation *translation; +} DEFAULT bhnd_bus_generic_get_dma_translation; /** * Attempt to read the BHND board identification from the parent bus. * * This relies on NVRAM access, and will fail if a valid NVRAM device cannot * be found, or is not yet attached. * * @param dev The parent of @p child. * @param child The bhnd device requesting board info. * @param[out] info On success, will be populated with the bhnd(4) device's * board information. * * @retval 0 success * @retval ENODEV No valid NVRAM source could be found. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ METHOD int read_board_info { device_t dev; device_t child; struct bhnd_board_info *info; } DEFAULT bhnd_bus_null_read_board_info; /** * Notify a bhnd bus that a child was added. * * This method must be called by concrete bhnd(4) driver impementations * after @p child's bus state is fully initialized. * * @param dev The bhnd bus whose child is being added. * @param child The child added to @p dev. */ METHOD void child_added { device_t dev; device_t child; } DEFAULT bhnd_bus_null_child_added; /** * Read the current value of @p child's I/O control register. * * @param dev The bhnd bus parent of @p child. * @param child The bhnd device for which the I/O control register should be * read. * @param[out] ioctl On success, the I/O control register value. * * @retval 0 success * @retval EINVAL If @p child is not a direct child of @p dev. * @retval ENODEV If agent/config space for @p child is unavailable. * @retval non-zero If reading the IOCTL register otherwise fails, a regular * unix error code will be returned. */ METHOD int read_ioctl { device_t dev; device_t child; uint16_t *ioctl; } DEFAULT bhnd_bus_null_read_ioctl; /** * Write @p value with @p mask to @p child's I/O control register. * * @param dev The bhnd bus parent of @p child. * @param child The bhnd device for which the I/O control register should * be updated. * @param value The value to be written (see also BHND_IOCTL_*). * @param mask Only the bits defined by @p mask will be updated from @p value. * * @retval 0 success * @retval EINVAL If @p child is not a direct child of @p dev. * @retval ENODEV If agent/config space for @p child is unavailable. * @retval non-zero If writing the IOCTL register otherwise fails, a regular * unix error code will be returned. */ METHOD int write_ioctl { device_t dev; device_t child; uint16_t value; uint16_t mask; } DEFAULT bhnd_bus_null_write_ioctl; /** * Read the current value of @p child's I/O status register. * * @param dev The bhnd bus parent of @p child. * @param child The bhnd device for which the I/O status register should be * read. * @param[out] iost On success, the I/O status register value. * * @retval 0 success * @retval EINVAL If @p child is not a direct child of @p dev. * @retval ENODEV If agent/config space for @p child is unavailable. * @retval non-zero If reading the IOST register otherwise fails, a regular * unix error code will be returned. */ METHOD int read_iost { device_t dev; device_t child; uint16_t *iost; } DEFAULT bhnd_bus_null_read_iost; /** * Return true if the given bhnd device's hardware is currently held * in a RESET state or otherwise not clocked (BHND_IOCTL_CLK_EN). * * @param dev The bhnd bus parent of @p child. * @param child The device to query. * * @retval true If @p child is held in RESET or not clocked (BHND_IOCTL_CLK_EN), * or an error occured determining @p child's hardware state. * @retval false If @p child is clocked and is not held in RESET. */ METHOD bool is_hw_suspended { device_t dev; device_t child; } DEFAULT bhnd_bus_null_is_hw_suspended; /** * Place the bhnd(4) device's hardware into a reset state, and then bring the * hardware out of reset with BHND_IOCTL_CLK_EN and @p ioctl flags set. * * Any clock or resource PMU requests previously made by @p child will be * invalidated. * * @param dev The bhnd bus parent of @p child. * @param child The device to be reset. * @param ioctl Device-specific core ioctl flags to be supplied on reset * (see BHND_IOCTL_*). * * @retval 0 success * @retval non-zero error */ METHOD int reset_hw { device_t dev; device_t child; uint16_t ioctl; } DEFAULT bhnd_bus_null_reset_hw; /** * Suspend @p child's hardware in a low-power reset state. * * Any clock or resource PMU requests previously made by @p dev will be * invalidated. * * The hardware may be brought out of reset via bhnd_reset_hw(). * * @param dev The bhnd bus parent of @P child. * @param dev The device to be suspended. * * @retval 0 success * @retval non-zero error */ METHOD int suspend_hw { device_t dev; device_t child; } DEFAULT bhnd_bus_null_suspend_hw; /** * If supported by the chipset, return the clock source for the given clock. * * This function is only supported on early PWRCTL-equipped chipsets * that expose clock management via their host bridge interface. Currently, * this includes PCI (not PCIe) devices, with ChipCommon core revisions 0-9. * * @param dev The parent of @p child. * @param child The bhnd device requesting a clock source. * @param clock The clock for which a clock source will be returned. * * @retval bhnd_clksrc The clock source for @p clock. * @retval BHND_CLKSRC_UNKNOWN If @p clock is unsupported, or its * clock source is not known to the bus. */ METHOD bhnd_clksrc pwrctl_get_clksrc { device_t dev; device_t child; bhnd_clock clock; } DEFAULT bhnd_bus_null_pwrctl_get_clksrc; /** * If supported by the chipset, gate the clock source for @p clock * * This function is only supported on early PWRCTL-equipped chipsets * that expose clock management via their host bridge interface. Currently, * this includes PCI (not PCIe) devices, with ChipCommon core revisions 0-9. * * @param dev The parent of @p child. * @param child The bhnd device requesting clock gating. * @param clock The clock to be disabled. * * @retval 0 success * @retval ENODEV If bus-level clock source management is not supported. * @retval ENXIO If bus-level management of @p clock is not supported. */ METHOD int pwrctl_gate_clock { device_t dev; device_t child; bhnd_clock clock; } DEFAULT bhnd_bus_null_pwrctl_gate_clock; /** * If supported by the chipset, ungate the clock source for @p clock * * This function is only supported on early PWRCTL-equipped chipsets * that expose clock management via their host bridge interface. Currently, * this includes PCI (not PCIe) devices, with ChipCommon core revisions 0-9. * * @param dev The parent of @p child. * @param child The bhnd device requesting clock gating. * @param clock The clock to be enabled. * * @retval 0 success * @retval ENODEV If bus-level clock source management is not supported. * @retval ENXIO If bus-level management of @p clock is not supported. */ METHOD int pwrctl_ungate_clock { device_t dev; device_t child; bhnd_clock clock; } DEFAULT bhnd_bus_null_pwrctl_ungate_clock; /** * Allocate and enable per-core PMU request handling for @p child. * * The region containing the core's PMU register block (if any) must be * allocated via bus_alloc_resource(9) (or bhnd_alloc_resource) before * calling BHND_BUS_ALLOC_PMU(), and must not be released until after * calling BHND_BUS_RELEASE_PMU(). * * @param dev The parent of @p child. * @param child The requesting bhnd device. */ METHOD int alloc_pmu { device_t dev; device_t child; } DEFAULT bhnd_bus_null_alloc_pmu; /** * Release per-core PMU resources allocated for @p child. Any * outstanding PMU requests are discarded. * * @param dev The parent of @p child. * @param child The requesting bhnd device. */ METHOD int release_pmu { device_t dev; device_t child; } DEFAULT bhnd_bus_null_release_pmu; /** * Request that @p clock (or faster) be routed to @p child. * * @note A driver must ask the bhnd bus to allocate PMU request state * via BHND_BUS_ALLOC_PMU() before it can request clock resources. * * @note Any outstanding PMU clock requests will be discarded upon calling * BHND_BUS_RESET_HW() or BHND_BUS_SUSPEND_HW(). * * @param dev The parent of @p child. * @param child The bhnd device requesting @p clock. * @param clock The requested clock source. * * @retval 0 success * @retval ENODEV If an unsupported clock was requested. * @retval ENXIO If the PMU has not been initialized or is otherwise unvailable. */ METHOD int request_clock { device_t dev; device_t child; bhnd_clock clock; } DEFAULT bhnd_bus_null_request_clock; /** * Request that @p clocks be powered on behalf of @p child. * * This will power on clock sources (e.g. XTAL, PLL, etc) required for * @p clocks and wait until they are ready, discarding any previous * requests by @p child. * * @note A driver must ask the bhnd bus to allocate PMU request state * via BHND_BUS_ALLOC_PMU() before it can request clock resources. * * @note Any outstanding PMU clock requests will be discarded upon calling * BHND_BUS_RESET_HW() or BHND_BUS_SUSPEND_HW(). * * @param dev The parent of @p child. * @param child The bhnd device requesting @p clock. * @param clock The requested clock source. * * @retval 0 success * @retval ENODEV If an unsupported clock was requested. * @retval ENXIO If the PMU has not been initialized or is otherwise unvailable. */ METHOD int enable_clocks { device_t dev; device_t child; uint32_t clocks; } DEFAULT bhnd_bus_null_enable_clocks; /** * Power up an external PMU-managed resource assigned to @p child. * * @note A driver must ask the bhnd bus to allocate PMU request state * via BHND_BUS_ALLOC_PMU() before it can request PMU resources. * * @note Any outstanding PMU resource requests will be released upon calling * BHND_BUS_RESET_HW() or BHND_BUS_SUSPEND_HW(). * * @param dev The parent of @p child. * @param child The bhnd device requesting @p rsrc. * @param rsrc The core-specific external resource identifier. * * @retval 0 success * @retval ENODEV If the PMU does not support @p rsrc. * @retval ENXIO If the PMU has not been initialized or is otherwise unvailable. */ METHOD int request_ext_rsrc { device_t dev; device_t child; u_int rsrc; } DEFAULT bhnd_bus_null_request_ext_rsrc; /** * Power down an external PMU-managed resource assigned to @p child. * * @note A driver must ask the bhnd bus to allocate PMU request state * via BHND_BUS_ALLOC_PMU() before it can request PMU resources. * * @param dev The parent of @p child. * @param child The bhnd device requesting @p rsrc. * @param rsrc The core-specific external resource number. * * @retval 0 success * @retval ENODEV If the PMU does not support @p rsrc. * @retval ENXIO If the PMU has not been initialized or is otherwise unvailable. */ METHOD int release_ext_rsrc { device_t dev; device_t child; u_int rsrc; } DEFAULT bhnd_bus_null_release_ext_rsrc; /** * Read @p width bytes at @p offset from the bus-specific agent/config * space of @p child. * * @param dev The parent of @p child. * @param child The bhnd device for which @p offset should be read. * @param offset The offset to be read. * @param[out] value On success, the bytes read at @p offset. * @param width The size of the access. Must be 1, 2 or 4 bytes. * * The exact behavior of this method is bus-specific. On a bcma(4) bus, this * method provides access to the first agent port of @p child; on a siba(4) bus, * this method provides access to the core's CFG0 register block. * * @note Device drivers should only use this API for functionality * that is not available via another bhnd(4) function. * * @retval 0 success * @retval EINVAL If @p child is not a direct child of @p dev. * @retval EINVAL If @p width is not one of 1, 2, or 4 bytes. * @retval ENODEV If accessing agent/config space for @p child is unsupported. * @retval EFAULT If reading @p width at @p offset exceeds the bounds of * the mapped agent/config space for @p child. */ METHOD int read_config { device_t dev; device_t child; bus_size_t offset; void *value; u_int width; } DEFAULT bhnd_bus_null_read_config; /** * Read @p width bytes at @p offset from the bus-specific agent/config * space of @p child. * * @param dev The parent of @p child. * @param child The bhnd device for which @p offset should be read. * @param offset The offset to be written. * @param value A pointer to the value to be written. * @param width The size of @p value. Must be 1, 2 or 4 bytes. * * The exact behavior of this method is bus-specific. In the case of * bcma(4), this method provides access to the first agent port of @p child. * * @note Device drivers should only use this API for functionality * that is not available via another bhnd(4) function. * * @retval 0 success * @retval EINVAL If @p child is not a direct child of @p dev. * @retval EINVAL If @p width is not one of 1, 2, or 4 bytes. * @retval ENODEV If accessing agent/config space for @p child is unsupported. * @retval EFAULT If reading @p width at @p offset exceeds the bounds of * the mapped agent/config space for @p child. */ METHOD int write_config { device_t dev; device_t child; bus_size_t offset; const void *value; u_int width; } DEFAULT bhnd_bus_null_write_config; /** * Allocate a bhnd resource. * * This method's semantics are functionally identical to the bus API of the same * name; refer to BUS_ALLOC_RESOURCE for complete documentation. */ METHOD struct bhnd_resource * alloc_resource { device_t dev; device_t child; int type; int *rid; rman_res_t start; rman_res_t end; rman_res_t count; u_int flags; } DEFAULT bhnd_bus_generic_alloc_resource; /** * Release a bhnd resource. * * This method's semantics are functionally identical to the bus API of the same * name; refer to BUS_RELEASE_RESOURCE for complete documentation. */ METHOD int release_resource { device_t dev; device_t child; int type; int rid; struct bhnd_resource *res; } DEFAULT bhnd_bus_generic_release_resource; /** * Activate a bhnd resource. * * This method's semantics are functionally identical to the bus API of the same * name; refer to BUS_ACTIVATE_RESOURCE for complete documentation. */ METHOD int activate_resource { device_t dev; device_t child; int type; int rid; struct bhnd_resource *r; } DEFAULT bhnd_bus_generic_activate_resource; /** * Deactivate a bhnd resource. * * This method's semantics are functionally identical to the bus API of the same * name; refer to BUS_DEACTIVATE_RESOURCE for complete documentation. */ METHOD int deactivate_resource { device_t dev; device_t child; int type; int rid; 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. * * @param dev The device whose child is being examined. * @param child The child device. * @param type The port type being queried. * @param port_num The port number being queried. * @param region_num The region number being queried. */ METHOD bool is_region_valid { device_t dev; device_t child; bhnd_port_type type; u_int port_num; u_int region_num; }; /** * Return the number of ports of type @p type attached to @p child. * * @param dev The device whose child is being examined. * @param child The child device. * @param type The port type being queried. */ METHOD u_int get_port_count { device_t dev; device_t child; bhnd_port_type type; }; /** * Return the number of memory regions mapped to @p child @p port of * type @p type. * * @param dev The device whose child is being examined. * @param child The child device. * @param port The port number being queried. * @param type The port type being queried. */ METHOD u_int get_region_count { device_t dev; device_t child; bhnd_port_type type; u_int port; }; /** * Return the SYS_RES_MEMORY resource-ID for a port/region pair attached to * @p child. * * @param dev The bus device. * @param child The bhnd child. * @param port_type The port type. * @param port_num The index of the child interconnect port. * @param region_num The index of the port-mapped address region. * * @retval -1 No such port/region found. */ METHOD int get_port_rid { device_t dev; device_t child; bhnd_port_type port_type; u_int port_num; u_int region_num; } DEFAULT bhnd_bus_null_get_port_rid; /** * Decode a port / region pair on @p child defined by @p type and @p rid. * * @param dev The bus device. * @param child The bhnd child. * @param type The resource type. * @param rid The resource ID. * @param[out] port_type The port's type. * @param[out] port The port identifier. * @param[out] region The identifier of the memory region on @p port. * * @retval 0 success * @retval non-zero No matching type/rid found. */ METHOD int decode_port_rid { device_t dev; device_t child; int type; int rid; bhnd_port_type *port_type; u_int *port; u_int *region; } DEFAULT bhnd_bus_null_decode_port_rid; /** * Get the address and size of @p region on @p port. * * @param dev The bus device. * @param child The bhnd child. * @param port_type The port type. * @param port The port identifier. * @param region The identifier of the memory region on @p port. * @param[out] region_addr The region's base address. * @param[out] region_size The region's size. * * @retval 0 success * @retval non-zero No matching port/region found. */ METHOD int get_region_addr { device_t dev; device_t child; bhnd_port_type port_type; u_int port; u_int region; bhnd_addr_t *region_addr; bhnd_size_t *region_size; } DEFAULT bhnd_bus_null_get_region_addr; /** * Read an NVRAM variable. * * It is the responsibility of the bus to delegate this request to * the appropriate NVRAM child device, or to a parent bus implementation. * * @param dev The bus device. * @param child The requesting device. * @param name The NVRAM variable name. * @param[out] buf On success, the requested value will be written * to this buffer. This argment may be NULL if * the value is not desired. * @param[in,out] size The capacity of @p buf. On success, will be set * to the actual size of the requested value. * @param type The data type to be written to @p buf. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENOMEM If @p buf is non-NULL and a buffer of @p size is too * small to hold the requested value. * @retval ENODEV No valid NVRAM source could be found. * @retval EFTYPE If the @p name's data type cannot be coerced to @p type. * @retval ERANGE If value coercion would overflow @p type. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ METHOD int get_nvram_var { device_t dev; device_t child; const char *name; void *buf; size_t *size; bhnd_nvram_type type; } DEFAULT bhnd_bus_null_get_nvram_var; /** An implementation of bus_read_1() compatible with bhnd_resource */ METHOD uint8_t read_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; } /** An implementation of bus_read_2() compatible with bhnd_resource */ METHOD uint16_t read_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; } /** An implementation of bus_read_4() compatible with bhnd_resource */ METHOD uint32_t read_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; } /** An implementation of bus_write_1() compatible with bhnd_resource */ METHOD void write_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t value; } /** An implementation of bus_write_2() compatible with bhnd_resource */ METHOD void write_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t value; } /** An implementation of bus_write_4() compatible with bhnd_resource */ METHOD void write_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t value; } /** An implementation of bus_read_stream_1() compatible with bhnd_resource */ METHOD uint8_t read_stream_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; } /** An implementation of bus_read_stream_2() compatible with bhnd_resource */ METHOD uint16_t read_stream_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; } /** An implementation of bus_read_stream_4() compatible with bhnd_resource */ METHOD uint32_t read_stream_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; } /** An implementation of bus_write_stream_1() compatible with bhnd_resource */ METHOD void write_stream_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t value; } /** An implementation of bus_write_stream_2() compatible with bhnd_resource */ METHOD void write_stream_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t value; } /** An implementation of bus_write_stream_4() compatible with bhnd_resource */ METHOD void write_stream_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t value; } /** An implementation of bus_read_multi_1() compatible with bhnd_resource */ METHOD void read_multi_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t *datap; bus_size_t count; } /** An implementation of bus_read_multi_2() compatible with bhnd_resource */ METHOD void read_multi_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t *datap; bus_size_t count; } /** An implementation of bus_read_multi_4() compatible with bhnd_resource */ METHOD void read_multi_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t *datap; bus_size_t count; } /** An implementation of bus_write_multi_1() compatible with bhnd_resource */ METHOD void write_multi_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t *datap; bus_size_t count; } /** An implementation of bus_write_multi_2() compatible with bhnd_resource */ METHOD void write_multi_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t *datap; bus_size_t count; } /** An implementation of bus_write_multi_4() compatible with bhnd_resource */ METHOD void write_multi_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t *datap; bus_size_t count; } /** An implementation of bus_read_multi_stream_1() compatible * bhnd_resource */ METHOD void read_multi_stream_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t *datap; bus_size_t count; } /** An implementation of bus_read_multi_stream_2() compatible * bhnd_resource */ METHOD void read_multi_stream_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t *datap; bus_size_t count; } /** An implementation of bus_read_multi_stream_4() compatible * bhnd_resource */ METHOD void read_multi_stream_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t *datap; bus_size_t count; } /** An implementation of bus_write_multi_stream_1() compatible * bhnd_resource */ METHOD void write_multi_stream_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t *datap; bus_size_t count; } /** An implementation of bus_write_multi_stream_2() compatible with * bhnd_resource */ METHOD void write_multi_stream_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t *datap; bus_size_t count; } /** An implementation of bus_write_multi_stream_4() compatible with * bhnd_resource */ METHOD void write_multi_stream_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t *datap; bus_size_t count; } /** An implementation of bus_set_multi_1() compatible with bhnd_resource */ METHOD void set_multi_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t value; bus_size_t count; } /** An implementation of bus_set_multi_2() compatible with bhnd_resource */ METHOD void set_multi_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t value; bus_size_t count; } /** An implementation of bus_set_multi_4() compatible with bhnd_resource */ METHOD void set_multi_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t value; bus_size_t count; } /** An implementation of bus_set_region_1() compatible with bhnd_resource */ METHOD void set_region_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t value; bus_size_t count; } /** An implementation of bus_set_region_2() compatible with bhnd_resource */ METHOD void set_region_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t value; bus_size_t count; } /** An implementation of bus_set_region_4() compatible with bhnd_resource */ METHOD void set_region_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t value; bus_size_t count; } /** An implementation of bus_read_region_1() compatible with bhnd_resource */ METHOD void read_region_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t *datap; bus_size_t count; } /** An implementation of bus_read_region_2() compatible with bhnd_resource */ METHOD void read_region_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t *datap; bus_size_t count; } /** An implementation of bus_read_region_4() compatible with bhnd_resource */ METHOD void read_region_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t *datap; bus_size_t count; } /** An implementation of bus_read_region_stream_1() compatible with * bhnd_resource */ METHOD void read_region_stream_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t *datap; bus_size_t count; } /** An implementation of bus_read_region_stream_2() compatible with * bhnd_resource */ METHOD void read_region_stream_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t *datap; bus_size_t count; } /** An implementation of bus_read_region_stream_4() compatible with * bhnd_resource */ METHOD void read_region_stream_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t *datap; bus_size_t count; } /** An implementation of bus_write_region_1() compatible with bhnd_resource */ METHOD void write_region_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t *datap; bus_size_t count; } /** An implementation of bus_write_region_2() compatible with bhnd_resource */ METHOD void write_region_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t *datap; bus_size_t count; } /** An implementation of bus_write_region_4() compatible with bhnd_resource */ METHOD void write_region_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t *datap; bus_size_t count; } /** An implementation of bus_write_region_stream_1() compatible with * bhnd_resource */ METHOD void write_region_stream_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t *datap; bus_size_t count; } /** An implementation of bus_write_region_stream_2() compatible with * bhnd_resource */ METHOD void write_region_stream_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t *datap; bus_size_t count; } /** An implementation of bus_write_region_stream_4() compatible with * bhnd_resource */ METHOD void write_region_stream_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t *datap; bus_size_t count; } /** An implementation of bus_barrier() compatible with bhnd_resource */ METHOD void barrier { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; bus_size_t length; int flags; } Index: head/sys/dev/bhnd/bhnd_subr.c =================================================================== --- head/sys/dev/bhnd/bhnd_subr.c (revision 326079) +++ head/sys/dev/bhnd/bhnd_subr.c (revision 326080) @@ -1,2333 +1,2354 @@ /*- * Copyright (c) 2015-2016 Landon Fuller * 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: * 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include "nvram/bhnd_nvram.h" #include "bhnd_chipc_if.h" #include "bhnd_nvram_if.h" #include "bhnd_nvram_map.h" #include "bhndreg.h" #include "bhndvar.h" #include "bhnd_private.h" static void bhnd_service_registry_free_entry( struct bhnd_service_entry *entry); static int compare_ascending_probe_order(const void *lhs, const void *rhs); static int compare_descending_probe_order(const void *lhs, const void *rhs); /* BHND core device description table. */ static const struct bhnd_core_desc { uint16_t vendor; uint16_t device; bhnd_devclass_t class; const char *desc; } bhnd_core_descs[] = { #define BHND_CDESC(_mfg, _cid, _cls, _desc) \ { BHND_MFGID_ ## _mfg, BHND_COREID_ ## _cid, \ BHND_DEVCLASS_ ## _cls, _desc } BHND_CDESC(BCM, CC, CC, "ChipCommon I/O Controller"), BHND_CDESC(BCM, ILINE20, OTHER, "iLine20 HPNA"), BHND_CDESC(BCM, SRAM, RAM, "SRAM"), BHND_CDESC(BCM, SDRAM, RAM, "SDRAM"), BHND_CDESC(BCM, PCI, PCI, "PCI Bridge"), BHND_CDESC(BCM, MIPS, CPU, "BMIPS CPU"), BHND_CDESC(BCM, ENET, ENET_MAC, "Fast Ethernet MAC"), BHND_CDESC(BCM, CODEC, OTHER, "V.90 Modem Codec"), BHND_CDESC(BCM, USB, USB_DUAL, "USB 1.1 Device/Host Controller"), BHND_CDESC(BCM, ADSL, OTHER, "ADSL Core"), BHND_CDESC(BCM, ILINE100, OTHER, "iLine100 HPNA"), BHND_CDESC(BCM, IPSEC, OTHER, "IPsec Accelerator"), BHND_CDESC(BCM, UTOPIA, OTHER, "UTOPIA ATM Core"), BHND_CDESC(BCM, PCMCIA, PCCARD, "PCMCIA Bridge"), BHND_CDESC(BCM, SOCRAM, RAM, "Internal Memory"), BHND_CDESC(BCM, MEMC, MEMC, "MEMC SDRAM Controller"), BHND_CDESC(BCM, OFDM, OTHER, "OFDM PHY"), BHND_CDESC(BCM, EXTIF, OTHER, "External Interface"), BHND_CDESC(BCM, D11, WLAN, "802.11 MAC/PHY/Radio"), BHND_CDESC(BCM, APHY, WLAN_PHY, "802.11a PHY"), BHND_CDESC(BCM, BPHY, WLAN_PHY, "802.11b PHY"), BHND_CDESC(BCM, GPHY, WLAN_PHY, "802.11g PHY"), BHND_CDESC(BCM, MIPS33, CPU, "BMIPS33 CPU"), BHND_CDESC(BCM, USB11H, USB_HOST, "USB 1.1 Host Controller"), BHND_CDESC(BCM, USB11D, USB_DEV, "USB 1.1 Device Controller"), BHND_CDESC(BCM, USB20H, USB_HOST, "USB 2.0 Host Controller"), BHND_CDESC(BCM, USB20D, USB_DEV, "USB 2.0 Device Controller"), BHND_CDESC(BCM, SDIOH, OTHER, "SDIO Host Controller"), BHND_CDESC(BCM, ROBO, OTHER, "RoboSwitch"), BHND_CDESC(BCM, ATA100, OTHER, "Parallel ATA Controller"), BHND_CDESC(BCM, SATAXOR, OTHER, "SATA DMA/XOR Controller"), BHND_CDESC(BCM, GIGETH, ENET_MAC, "Gigabit Ethernet MAC"), BHND_CDESC(BCM, PCIE, PCIE, "PCIe Bridge"), BHND_CDESC(BCM, NPHY, WLAN_PHY, "802.11n 2x2 PHY"), BHND_CDESC(BCM, SRAMC, MEMC, "SRAM Controller"), BHND_CDESC(BCM, MINIMAC, OTHER, "MINI MAC/PHY"), BHND_CDESC(BCM, ARM11, CPU, "ARM1176 CPU"), BHND_CDESC(BCM, ARM7S, CPU, "ARM7TDMI-S CPU"), BHND_CDESC(BCM, LPPHY, WLAN_PHY, "802.11a/b/g PHY"), BHND_CDESC(BCM, PMU, PMU, "PMU"), BHND_CDESC(BCM, SSNPHY, WLAN_PHY, "802.11n Single-Stream PHY"), BHND_CDESC(BCM, SDIOD, OTHER, "SDIO Device Core"), BHND_CDESC(BCM, ARMCM3, CPU, "ARM Cortex-M3 CPU"), BHND_CDESC(BCM, HTPHY, WLAN_PHY, "802.11n 4x4 PHY"), BHND_CDESC(MIPS,MIPS74K, CPU, "MIPS74k CPU"), BHND_CDESC(BCM, GMAC, ENET_MAC, "Gigabit MAC core"), BHND_CDESC(BCM, DMEMC, MEMC, "DDR1/DDR2 Memory Controller"), BHND_CDESC(BCM, PCIERC, OTHER, "PCIe Root Complex"), BHND_CDESC(BCM, OCP, SOC_BRIDGE, "OCP to OCP Bridge"), BHND_CDESC(BCM, SC, OTHER, "Shared Common Core"), BHND_CDESC(BCM, AHB, SOC_BRIDGE, "OCP to AHB Bridge"), BHND_CDESC(BCM, SPIH, OTHER, "SPI Host Controller"), BHND_CDESC(BCM, I2S, OTHER, "I2S Digital Audio Interface"), BHND_CDESC(BCM, DMEMS, MEMC, "SDR/DDR1 Memory Controller"), BHND_CDESC(BCM, UBUS_SHIM, OTHER, "BCM6362/UBUS WLAN SHIM"), BHND_CDESC(BCM, PCIE2, PCIE, "PCIe Bridge (Gen2)"), BHND_CDESC(ARM, APB_BRIDGE, SOC_BRIDGE, "BP135 AMBA3 AXI to APB Bridge"), BHND_CDESC(ARM, PL301, SOC_ROUTER, "PL301 AMBA3 Interconnect"), BHND_CDESC(ARM, EROM, EROM, "PL366 Device Enumeration ROM"), BHND_CDESC(ARM, OOB_ROUTER, OTHER, "PL367 OOB Interrupt Router"), BHND_CDESC(ARM, AXI_UNMAPPED, OTHER, "Unmapped Address Ranges"), BHND_CDESC(BCM, 4706_CC, CC, "ChipCommon I/O Controller"), BHND_CDESC(BCM, NS_PCIE2, PCIE, "PCIe Bridge (Gen2)"), BHND_CDESC(BCM, NS_DMA, OTHER, "DMA engine"), BHND_CDESC(BCM, NS_SDIO, OTHER, "SDIO 3.0 Host Controller"), BHND_CDESC(BCM, NS_USB20H, USB_HOST, "USB 2.0 Host Controller"), BHND_CDESC(BCM, NS_USB30H, USB_HOST, "USB 3.0 Host Controller"), BHND_CDESC(BCM, NS_A9JTAG, OTHER, "ARM Cortex A9 JTAG Interface"), BHND_CDESC(BCM, NS_DDR23_MEMC, MEMC, "Denali DDR2/DD3 Memory Controller"), BHND_CDESC(BCM, NS_ROM, NVRAM, "System ROM"), BHND_CDESC(BCM, NS_NAND, NVRAM, "NAND Flash Controller"), BHND_CDESC(BCM, NS_QSPI, NVRAM, "QSPI Flash Controller"), BHND_CDESC(BCM, NS_CC_B, CC_B, "ChipCommon B Auxiliary I/O Controller"), BHND_CDESC(BCM, 4706_SOCRAM, RAM, "Internal Memory"), BHND_CDESC(BCM, IHOST_ARMCA9, CPU, "ARM Cortex A9 CPU"), BHND_CDESC(BCM, 4706_GMAC_CMN, ENET, "Gigabit MAC (Common)"), BHND_CDESC(BCM, 4706_GMAC, ENET_MAC, "Gigabit MAC"), BHND_CDESC(BCM, AMEMC, MEMC, "Denali DDR1/DDR2 Memory Controller"), #undef BHND_CDESC /* Derived from inspection of the BCM4331 cores that provide PrimeCell * IDs. Due to lack of documentation, the surmised device name/purpose * provided here may be incorrect. */ { BHND_MFGID_ARM, BHND_PRIMEID_EROM, BHND_DEVCLASS_OTHER, "PL364 Device Enumeration ROM" }, { BHND_MFGID_ARM, BHND_PRIMEID_SWRAP, BHND_DEVCLASS_OTHER, "PL368 Device Management Interface" }, { BHND_MFGID_ARM, BHND_PRIMEID_MWRAP, BHND_DEVCLASS_OTHER, "PL369 Device Management Interface" }, { 0, 0, 0, NULL } }; /** * Return the name for a given JEP106 manufacturer ID. * * @param vendor A JEP106 Manufacturer ID, including the non-standard ARM 4-bit * JEP106 continuation code. */ const char * bhnd_vendor_name(uint16_t vendor) { switch (vendor) { case BHND_MFGID_ARM: return "ARM"; case BHND_MFGID_BCM: return "Broadcom"; case BHND_MFGID_MIPS: return "MIPS"; default: return "unknown"; } } /** * Return the name of a port type. */ const char * bhnd_port_type_name(bhnd_port_type port_type) { switch (port_type) { case BHND_PORT_DEVICE: return ("device"); case BHND_PORT_BRIDGE: return ("bridge"); case BHND_PORT_AGENT: return ("agent"); default: return "unknown"; } } /** * Return the name of an NVRAM source. */ const char * bhnd_nvram_src_name(bhnd_nvram_src nvram_src) { switch (nvram_src) { case BHND_NVRAM_SRC_FLASH: return ("flash"); case BHND_NVRAM_SRC_OTP: return ("OTP"); case BHND_NVRAM_SRC_SPROM: return ("SPROM"); case BHND_NVRAM_SRC_UNKNOWN: return ("none"); default: return ("unknown"); } } static const struct bhnd_core_desc * bhnd_find_core_desc(uint16_t vendor, uint16_t device) { for (u_int i = 0; bhnd_core_descs[i].desc != NULL; i++) { if (bhnd_core_descs[i].vendor != vendor) continue; if (bhnd_core_descs[i].device != device) continue; return (&bhnd_core_descs[i]); } return (NULL); } /** * Return a human-readable name for a BHND core. * * @param vendor The core designer's JEDEC-106 Manufacturer ID * @param device The core identifier. */ const char * bhnd_find_core_name(uint16_t vendor, uint16_t device) { const struct bhnd_core_desc *desc; if ((desc = bhnd_find_core_desc(vendor, device)) == NULL) return ("unknown"); return desc->desc; } /** * Return the device class for a BHND core. * * @param vendor The core designer's JEDEC-106 Manufacturer ID * @param device The core identifier. */ bhnd_devclass_t bhnd_find_core_class(uint16_t vendor, uint16_t device) { const struct bhnd_core_desc *desc; if ((desc = bhnd_find_core_desc(vendor, device)) == NULL) return (BHND_DEVCLASS_OTHER); return desc->class; } /** * Return a human-readable name for a BHND core. * * @param ci The core's info record. */ const char * bhnd_core_name(const struct bhnd_core_info *ci) { return bhnd_find_core_name(ci->vendor, ci->device); } /** * Return the device class for a BHND core. * * @param ci The core's info record. */ bhnd_devclass_t bhnd_core_class(const struct bhnd_core_info *ci) { return bhnd_find_core_class(ci->vendor, ci->device); } /** * Write a human readable name representation of the given * BHND_CHIPID_* constant to @p buffer. * * @param buffer Output buffer, or NULL to compute the required size. * @param size Capacity of @p buffer, in bytes. * @param chip_id Chip ID to be formatted. * * @return Returns the required number of bytes on success, or a negative * integer on failure. No more than @p size-1 characters be written, with * the @p size'th set to '\0'. * * @sa BHND_CHIPID_MAX_NAMELEN */ int bhnd_format_chip_id(char *buffer, size_t size, uint16_t chip_id) { /* All hex formatted IDs are within the range of 0x4000-0x9C3F (40000-1) */ if (chip_id >= 0x4000 && chip_id <= 0x9C3F) return (snprintf(buffer, size, "BCM%hX", chip_id)); else return (snprintf(buffer, size, "BCM%hu", chip_id)); } /** * Initialize a core info record with data from from a bhnd-attached @p dev. * * @param dev A bhnd device. * @param core The record to be initialized. */ struct bhnd_core_info bhnd_get_core_info(device_t dev) { return (struct bhnd_core_info) { .vendor = bhnd_get_vendor(dev), .device = bhnd_get_device(dev), .hwrev = bhnd_get_hwrev(dev), .core_idx = bhnd_get_core_index(dev), .unit = bhnd_get_core_unit(dev) }; } /** * Find a @p class child device with @p unit on @p bus. * * @param bus The bhnd-compatible bus to be searched. * @param class The device class to match on. * @param unit The core unit number; specify -1 to return the first match * regardless of unit number. * * @retval device_t if a matching child device is found. * @retval NULL if no matching child device is found. */ device_t bhnd_bus_find_child(device_t bus, bhnd_devclass_t class, int unit) { struct bhnd_core_match md = { BHND_MATCH_CORE_CLASS(class), BHND_MATCH_CORE_UNIT(unit) }; if (unit == -1) md.m.match.core_unit = 0; return bhnd_bus_match_child(bus, &md); } /** * Find the first child device on @p bus that matches @p desc. * * @param bus The bhnd-compatible bus to be searched. * @param desc A match descriptor. * * @retval device_t if a matching child device is found. * @retval NULL if no matching child device is found. */ device_t bhnd_bus_match_child(device_t bus, const struct bhnd_core_match *desc) { device_t *devlistp; device_t match; int devcnt; int error; error = device_get_children(bus, &devlistp, &devcnt); if (error != 0) return (NULL); match = NULL; for (int i = 0; i < devcnt; i++) { struct bhnd_core_info ci = bhnd_get_core_info(devlistp[i]); if (bhnd_core_matches(&ci, desc)) { match = devlistp[i]; goto done; } } done: free(devlistp, M_TEMP); return match; } /** * Retrieve an ordered list of all device instances currently connected to * @p bus, returning a pointer to the array in @p devlistp and the count * in @p ndevs. * * The memory allocated for the table must be freed via * bhnd_bus_free_children(). * * @param bus The bhnd-compatible bus to be queried. * @param[out] devlist The array of devices. * @param[out] devcount The number of devices in @p devlistp * @param order The order in which devices will be returned * in @p devlist. * * @retval 0 success * @retval non-zero if an error occurs, a regular unix error code will * be returned. */ int bhnd_bus_get_children(device_t bus, device_t **devlist, int *devcount, bhnd_device_order order) { int error; /* Fetch device array */ if ((error = device_get_children(bus, devlist, devcount))) return (error); /* Perform requested sorting */ if ((error = bhnd_sort_devices(*devlist, *devcount, order))) { bhnd_bus_free_children(*devlist); return (error); } return (0); } /** * Free any memory allocated in a previous call to bhnd_bus_get_children(). * * @param devlist The device array returned by bhnd_bus_get_children(). */ void bhnd_bus_free_children(device_t *devlist) { free(devlist, M_TEMP); } /** * Perform in-place sorting of an array of bhnd device instances. * * @param devlist An array of bhnd devices. * @param devcount The number of devices in @p devs. * @param order The sort order to be used. */ int bhnd_sort_devices(device_t *devlist, size_t devcount, bhnd_device_order order) { int (*compare)(const void *, const void *); switch (order) { case BHND_DEVICE_ORDER_ATTACH: compare = compare_ascending_probe_order; break; case BHND_DEVICE_ORDER_DETACH: compare = compare_descending_probe_order; break; default: printf("unknown sort order: %d\n", order); return (EINVAL); } qsort(devlist, devcount, sizeof(*devlist), compare); return (0); } /* * Ascending comparison of bhnd device's probe order. */ static int compare_ascending_probe_order(const void *lhs, const void *rhs) { device_t ldev, rdev; int lorder, rorder; ldev = (*(const device_t *) lhs); rdev = (*(const device_t *) rhs); lorder = BHND_BUS_GET_PROBE_ORDER(device_get_parent(ldev), ldev); rorder = BHND_BUS_GET_PROBE_ORDER(device_get_parent(rdev), rdev); if (lorder < rorder) { return (-1); } else if (lorder > rorder) { return (1); } else { return (0); } } /* * Descending comparison of bhnd device's probe order. */ static int compare_descending_probe_order(const void *lhs, const void *rhs) { return (compare_ascending_probe_order(rhs, lhs)); } /** * Call device_probe_and_attach() for each of the bhnd bus device's * children, in bhnd attach order. * * @param bus The bhnd-compatible bus for which all children should be probed * and attached. */ int bhnd_bus_probe_children(device_t bus) { device_t *devs; int ndevs; int error; /* Fetch children in attach order */ error = bhnd_bus_get_children(bus, &devs, &ndevs, BHND_DEVICE_ORDER_ATTACH); if (error) return (error); /* Probe and attach all children */ for (int i = 0; i < ndevs; i++) { device_t child = devs[i]; device_probe_and_attach(child); } bhnd_bus_free_children(devs); return (0); } /** * Walk up the bhnd device hierarchy to locate the root device * to which the bhndb bridge is attached. * * This can be used from within bhnd host bridge drivers to locate the * actual upstream host device. * * @param dev A bhnd device. * @param bus_class The expected bus (e.g. "pci") to which the bridge root * should be attached. * * @retval device_t if a matching parent device is found. * @retval NULL @p dev is not attached via a bhndb bus * @retval NULL no parent device is attached via @p bus_class. */ device_t bhnd_find_bridge_root(device_t dev, devclass_t bus_class) { devclass_t bhndb_class; device_t parent; KASSERT(device_get_devclass(device_get_parent(dev)) == bhnd_devclass, ("%s not a bhnd device", device_get_nameunit(dev))); bhndb_class = devclass_find("bhndb"); /* Walk the device tree until we hit a bridge */ parent = dev; while ((parent = device_get_parent(parent)) != NULL) { if (device_get_devclass(parent) == bhndb_class) break; } /* No bridge? */ if (parent == NULL) return (NULL); /* Search for a parent attached to the expected bus class */ while ((parent = device_get_parent(parent)) != NULL) { device_t bus; bus = device_get_parent(parent); if (bus != NULL && device_get_devclass(bus) == bus_class) return (parent); } /* Not found */ return (NULL); } /** * Find the first core in @p cores that matches @p desc. * * @param cores The table to search. * @param num_cores The length of @p cores. * @param desc A match descriptor. * * @retval bhnd_core_info if a matching core is found. * @retval NULL if no matching core is found. */ const struct bhnd_core_info * bhnd_match_core(const struct bhnd_core_info *cores, u_int num_cores, const struct bhnd_core_match *desc) { for (u_int i = 0; i < num_cores; i++) { if (bhnd_core_matches(&cores[i], desc)) return &cores[i]; } return (NULL); } /** * Find the first core in @p cores with the given @p class. * * @param cores The table to search. * @param num_cores The length of @p cores. * @param desc A match descriptor. * * @retval bhnd_core_info if a matching core is found. * @retval NULL if no matching core is found. */ const struct bhnd_core_info * bhnd_find_core(const struct bhnd_core_info *cores, u_int num_cores, bhnd_devclass_t class) { struct bhnd_core_match md = { BHND_MATCH_CORE_CLASS(class) }; return bhnd_match_core(cores, num_cores, &md); } /** * Create an equality match descriptor for @p core. * * @param core The core info to be matched on. * @param desc On return, will be populated with a match descriptor for @p core. */ struct bhnd_core_match bhnd_core_get_match_desc(const struct bhnd_core_info *core) { return ((struct bhnd_core_match) { BHND_MATCH_CORE_VENDOR(core->vendor), BHND_MATCH_CORE_ID(core->device), BHND_MATCH_CORE_REV(HWREV_EQ(core->hwrev)), BHND_MATCH_CORE_CLASS(bhnd_core_class(core)), BHND_MATCH_CORE_IDX(core->core_idx), BHND_MATCH_CORE_UNIT(core->unit) }); } /** * Return true if the @p lhs is equal to @p rhs * * @param lhs The first bhnd core descriptor to compare. * @param rhs The second bhnd core descriptor to compare. * * @retval true if @p lhs is equal to @p rhs * @retval false if @p lhs is not equal to @p rhs */ bool bhnd_cores_equal(const struct bhnd_core_info *lhs, const struct bhnd_core_info *rhs) { struct bhnd_core_match md; /* Use an equality match descriptor to perform the comparison */ md = bhnd_core_get_match_desc(rhs); return (bhnd_core_matches(lhs, &md)); } /** * Return true if the @p core matches @p desc. * * @param core A bhnd core descriptor. * @param desc A match descriptor to compare against @p core. * * @retval true if @p core matches @p match * @retval false if @p core does not match @p match. */ bool bhnd_core_matches(const struct bhnd_core_info *core, const struct bhnd_core_match *desc) { if (desc->m.match.core_vendor && desc->core_vendor != core->vendor) return (false); if (desc->m.match.core_id && desc->core_id != core->device) return (false); if (desc->m.match.core_unit && desc->core_unit != core->unit) return (false); if (desc->m.match.core_rev && !bhnd_hwrev_matches(core->hwrev, &desc->core_rev)) return (false); if (desc->m.match.core_idx && desc->core_idx != core->core_idx) return (false); if (desc->m.match.core_class && desc->core_class != bhnd_core_class(core)) return (false); return true; } /** * Return true if the @p chip matches @p desc. * * @param chip A bhnd chip identifier. * @param desc A match descriptor to compare against @p chip. * * @retval true if @p chip matches @p match * @retval false if @p chip does not match @p match. */ bool bhnd_chip_matches(const struct bhnd_chipid *chip, const struct bhnd_chip_match *desc) { if (desc->m.match.chip_id && chip->chip_id != desc->chip_id) return (false); if (desc->m.match.chip_pkg && chip->chip_pkg != desc->chip_pkg) return (false); if (desc->m.match.chip_rev && !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); } /** * Return true if the @p board matches @p desc. * * @param board The bhnd board info. * @param desc A match descriptor to compare against @p board. * * @retval true if @p chip matches @p match * @retval false if @p chip does not match @p match. */ bool bhnd_board_matches(const struct bhnd_board_info *board, const struct bhnd_board_match *desc) { if (desc->m.match.board_srom_rev && !bhnd_hwrev_matches(board->board_srom_rev, &desc->board_srom_rev)) return (false); if (desc->m.match.board_vendor && board->board_vendor != desc->board_vendor) return (false); if (desc->m.match.board_type && board->board_type != desc->board_type) return (false); if (desc->m.match.board_rev && !bhnd_hwrev_matches(board->board_rev, &desc->board_rev)) return (false); return (true); } /** * Return true if the @p hwrev matches @p desc. * * @param hwrev A bhnd hardware revision. * @param desc A match descriptor to compare against @p core. * * @retval true if @p hwrev matches @p match * @retval false if @p hwrev does not match @p match. */ bool bhnd_hwrev_matches(uint16_t hwrev, const struct bhnd_hwrev_match *desc) { if (desc->start != BHND_HWREV_INVALID && desc->start > hwrev) return false; if (desc->end != BHND_HWREV_INVALID && desc->end < hwrev) return false; return true; } /** * Return true if the @p dev matches @p desc. * * @param dev A bhnd device. * @param desc A match descriptor to compare against @p dev. * * @retval true if @p dev matches @p match * @retval false if @p dev does not match @p match. */ bool bhnd_device_matches(device_t dev, const struct bhnd_device_match *desc) { struct bhnd_core_info core; const struct bhnd_chipid *chip; struct bhnd_board_info board; device_t parent; int error; /* Construct individual match descriptors */ struct bhnd_core_match m_core = { _BHND_CORE_MATCH_COPY(desc) }; struct bhnd_chip_match m_chip = { _BHND_CHIP_MATCH_COPY(desc) }; struct bhnd_board_match m_board = { _BHND_BOARD_MATCH_COPY(desc) }; /* Fetch and match core info */ if (m_core.m.match_flags) { /* Only applicable to bhnd-attached cores */ parent = device_get_parent(dev); if (device_get_devclass(parent) != bhnd_devclass) { device_printf(dev, "attempting to match core " "attributes against non-core device\n"); return (false); } core = bhnd_get_core_info(dev); if (!bhnd_core_matches(&core, &m_core)) return (false); } /* Fetch and match chip info */ if (m_chip.m.match_flags) { chip = bhnd_get_chipid(dev); if (!bhnd_chip_matches(chip, &m_chip)) return (false); } /* Fetch and match board info. * * This is not available until after NVRAM is up; earlier device * matches should not include board requirements */ if (m_board.m.match_flags) { if ((error = bhnd_read_board_info(dev, &board))) { device_printf(dev, "failed to read required board info " "during device matching: %d\n", error); return (false); } if (!bhnd_board_matches(&board, &m_board)) return (false); } /* All matched */ return (true); } /** * Search @p table for an entry matching @p dev. * * @param dev A bhnd device to match against @p table. * @param table The device table to search. * @param entry_size The @p table entry size, in bytes. * * @retval bhnd_device the first matching device, if any. * @retval NULL if no matching device is found in @p table. */ const struct bhnd_device * bhnd_device_lookup(device_t dev, const struct bhnd_device *table, size_t entry_size) { const struct bhnd_device *entry; device_t hostb, parent; bhnd_attach_type attach_type; uint32_t dflags; parent = device_get_parent(dev); hostb = bhnd_bus_find_hostb_device(parent); attach_type = bhnd_get_attach_type(dev); for (entry = table; !BHND_DEVICE_IS_END(entry); entry = (const struct bhnd_device *) ((const char *) entry + entry_size)) { /* match core info */ if (!bhnd_device_matches(dev, &entry->core)) continue; /* match device flags */ dflags = entry->device_flags; /* hostb implies BHND_ATTACH_ADAPTER requirement */ if (dflags & BHND_DF_HOSTB) dflags |= BHND_DF_ADAPTER; if (dflags & BHND_DF_ADAPTER) if (attach_type != BHND_ATTACH_ADAPTER) continue; if (dflags & BHND_DF_HOSTB) if (dev != hostb) continue; if (dflags & BHND_DF_SOC) if (attach_type != BHND_ATTACH_NATIVE) continue; /* device found */ return (entry); } /* not found */ return (NULL); } /** * Scan the device @p table for all quirk flags applicable to @p dev. * * @param dev A bhnd device to match against @p table. * @param table The device table to search. * * @return returns all matching quirk flags. */ uint32_t bhnd_device_quirks(device_t dev, const struct bhnd_device *table, size_t entry_size) { const struct bhnd_device *dent; const struct bhnd_device_quirk *qent, *qtable; uint32_t quirks; /* Locate the device entry */ if ((dent = bhnd_device_lookup(dev, table, entry_size)) == NULL) return (0); /* Quirks table is optional */ qtable = dent->quirks_table; if (qtable == NULL) return (0); /* Collect matching device quirk entries */ quirks = 0; for (qent = qtable; !BHND_DEVICE_QUIRK_IS_END(qent); qent++) { if (bhnd_device_matches(dev, &qent->desc)) quirks |= qent->quirks; } return (quirks); } /** * Allocate bhnd(4) resources defined in @p rs from a parent bus. * * @param dev The device requesting ownership of the resources. * @param rs A standard bus resource specification. This will be updated * with the allocated resource's RIDs. * @param res On success, the allocated bhnd resources. * * @retval 0 success * @retval non-zero if allocation of any non-RF_OPTIONAL resource fails, * all allocated resources will be released and a regular * unix error code will be returned. */ int bhnd_alloc_resources(device_t dev, struct resource_spec *rs, struct bhnd_resource **res) { /* Initialize output array */ for (u_int i = 0; rs[i].type != -1; i++) res[i] = NULL; for (u_int i = 0; rs[i].type != -1; i++) { res[i] = bhnd_alloc_resource_any(dev, rs[i].type, &rs[i].rid, rs[i].flags); /* Clean up all allocations on failure */ if (res[i] == NULL && !(rs[i].flags & RF_OPTIONAL)) { bhnd_release_resources(dev, rs, res); return (ENXIO); } } return (0); } /** * Release bhnd(4) resources defined in @p rs from a parent bus. * * @param dev The device that owns the resources. * @param rs A standard bus resource specification previously initialized * by @p bhnd_alloc_resources. * @param res The bhnd resources to be released. */ void bhnd_release_resources(device_t dev, const struct resource_spec *rs, struct bhnd_resource **res) { for (u_int i = 0; rs[i].type != -1; i++) { if (res[i] == NULL) continue; bhnd_release_resource(dev, rs[i].type, rs[i].rid, res[i]); res[i] = NULL; } } /** * Parse the CHIPC_ID_* fields from the ChipCommon CHIPC_ID * register, returning its bhnd_chipid representation. * * @param idreg The CHIPC_ID register value. * @param enum_addr The enumeration address to include in the result. * * @warning * On early siba(4) devices, the ChipCommon core does not provide * a valid CHIPC_ID_NUMCORE field. On these ChipCommon revisions * (see CHIPC_NCORES_MIN_HWREV()), this function will parse and return * an invalid `ncores` value. */ struct bhnd_chipid bhnd_parse_chipid(uint32_t idreg, bhnd_addr_t enum_addr) { struct bhnd_chipid result; /* Fetch the basic chip info */ result.chip_id = CHIPC_GET_BITS(idreg, CHIPC_ID_CHIP); result.chip_pkg = CHIPC_GET_BITS(idreg, CHIPC_ID_PKG); result.chip_rev = CHIPC_GET_BITS(idreg, CHIPC_ID_REV); result.chip_type = CHIPC_GET_BITS(idreg, CHIPC_ID_BUS); result.ncores = CHIPC_GET_BITS(idreg, CHIPC_ID_NUMCORE); result.enum_addr = enum_addr; return (result); } /** * Determine the correct core count for a chip identification value that * may contain an invalid core count. * * On some early siba(4) devices (see CHIPC_NCORES_MIN_HWREV()), the ChipCommon * core does not provide a valid CHIPC_ID_NUMCORE field. * * @param cid The chip identification to be queried. * @param chipc_hwrev The hardware revision of the ChipCommon core from which * @p cid was parsed. * @param[out] ncores On success, will be set to the correct core count. * * @retval 0 If the core count is already correct, or was mapped to a * a correct value. * @retval EINVAL If the core count is incorrect, but the chip was not * recognized. */ int bhnd_chipid_fixed_ncores(const struct bhnd_chipid *cid, uint16_t chipc_hwrev, uint8_t *ncores) { /* bcma(4), and most siba(4) devices */ if (CHIPC_NCORES_MIN_HWREV(chipc_hwrev)) { *ncores = cid->ncores; return (0); } /* broken siba(4) chipsets */ switch (cid->chip_id) { case BHND_CHIPID_BCM4306: *ncores = 6; break; case BHND_CHIPID_BCM4704: *ncores = 9; break; case BHND_CHIPID_BCM5365: /* * BCM5365 does support ID_NUMCORE in at least * some of its revisions, but for unknown * reasons, Broadcom's drivers always exclude * the ChipCommon revision (0x5) used by BCM5365 * from the set of revisions supporting * ID_NUMCORE, and instead supply a fixed value. * * Presumably, at least some of these devices * shipped with a broken ID_NUMCORE value. */ *ncores = 7; break; default: return (EINVAL); } return (0); } /** * Allocate the resource defined by @p rs via @p dev, use it * to read the ChipCommon ID register relative to @p chipc_offset, * then release the resource. * * @param dev The device owning @p rs. * @param rs A resource spec that encompasses the ChipCommon register block. * @param chipc_offset The offset of the ChipCommon registers within @p rs. * @param[out] result the chip identification data. * * @retval 0 success * @retval non-zero if the ChipCommon identification data could not be read. */ int bhnd_read_chipid(device_t dev, struct resource_spec *rs, bus_size_t chipc_offset, struct bhnd_chipid *result) { struct resource *res; bhnd_addr_t enum_addr; uint32_t reg; uint8_t chip_type; int error, rid, rtype; rid = rs->rid; rtype = rs->type; error = 0; /* Allocate the ChipCommon window resource and fetch the chipid data */ res = bus_alloc_resource_any(dev, rtype, &rid, RF_ACTIVE); if (res == NULL) { device_printf(dev, "failed to allocate bhnd chipc resource\n"); return (ENXIO); } /* Fetch the basic chip info */ reg = bus_read_4(res, chipc_offset + CHIPC_ID); chip_type = CHIPC_GET_BITS(reg, CHIPC_ID_BUS); /* Fetch the EROMPTR */ if (BHND_CHIPTYPE_HAS_EROM(chip_type)) { enum_addr = bus_read_4(res, chipc_offset + CHIPC_EROMPTR); } else if (chip_type == BHND_CHIPTYPE_SIBA) { /* siba(4) uses the ChipCommon base address as the enumeration * address */ enum_addr = BHND_DEFAULT_CHIPC_ADDR; } else { device_printf(dev, "unknown chip type %hhu\n", chip_type); error = ENODEV; goto cleanup; } *result = bhnd_parse_chipid(reg, enum_addr); /* Fix the core count on early siba(4) devices */ if (chip_type == BHND_CHIPTYPE_SIBA) { uint32_t idh; uint16_t chipc_hwrev; /* * We need the ChipCommon revision to determine whether * the ncore field is valid. * * We can safely assume the siba IDHIGH register is mapped * within the chipc register block. */ idh = bus_read_4(res, SB0_REG_ABS(SIBA_CFG0_IDHIGH)); chipc_hwrev = SIBA_IDH_CORE_REV(idh); error = bhnd_chipid_fixed_ncores(result, chipc_hwrev, &result->ncores); if (error) goto cleanup; } cleanup: /* Clean up */ bus_release_resource(dev, rtype, rid, res); return (error); } /** * Read an NVRAM variable's NUL-terminated string value. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] buf A buffer large enough to hold @p len bytes. On * success, the NUL-terminated string value will be * written to this buffer. This argment may be NULL if * the value is not desired. * @param len The maximum capacity of @p buf. * @param[out] rlen On success, will be set to the actual size of * the requested value (including NUL termination). This * argment may be NULL if the size is not desired. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval ENOMEM If @p buf is non-NULL and a buffer of @p len is too * small to hold the requested value. * @retval EFTYPE If the variable data cannot be coerced to a valid * string representation. * @retval ERANGE If value coercion would overflow @p type. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_getvar_str(device_t dev, const char *name, char *buf, size_t len, size_t *rlen) { size_t larg; int error; larg = len; error = bhnd_nvram_getvar(dev, name, buf, &larg, BHND_NVRAM_TYPE_STRING); if (rlen != NULL) *rlen = larg; return (error); } /** * Read an NVRAM variable's unsigned integer value. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] value On success, the requested value will be written * to this pointer. * @param width The output integer type width (1, 2, or * 4 bytes). * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval EFTYPE If the variable data cannot be coerced to a * a valid unsigned integer representation. * @retval ERANGE If value coercion would overflow (or underflow) an * unsigned representation of the given @p width. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_getvar_uint(device_t dev, const char *name, void *value, int width) { bhnd_nvram_type type; size_t len; switch (width) { case 1: type = BHND_NVRAM_TYPE_UINT8; break; case 2: type = BHND_NVRAM_TYPE_UINT16; break; case 4: type = BHND_NVRAM_TYPE_UINT32; break; default: device_printf(dev, "unsupported NVRAM integer width: %d\n", width); return (EINVAL); } len = width; return (bhnd_nvram_getvar(dev, name, value, &len, type)); } /** * Read an NVRAM variable's unsigned 8-bit integer value. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] value On success, the requested value will be written * to this pointer. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval EFTYPE If the variable data cannot be coerced to a * a valid unsigned integer representation. * @retval ERANGE If value coercion would overflow (or underflow) uint8_t. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_getvar_uint8(device_t dev, const char *name, uint8_t *value) { return (bhnd_nvram_getvar_uint(dev, name, value, sizeof(*value))); } /** * Read an NVRAM variable's unsigned 16-bit integer value. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] value On success, the requested value will be written * to this pointer. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval EFTYPE If the variable data cannot be coerced to a * a valid unsigned integer representation. * @retval ERANGE If value coercion would overflow (or underflow) * uint16_t. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_getvar_uint16(device_t dev, const char *name, uint16_t *value) { return (bhnd_nvram_getvar_uint(dev, name, value, sizeof(*value))); } /** * Read an NVRAM variable's unsigned 32-bit integer value. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] value On success, the requested value will be written * to this pointer. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval EFTYPE If the variable data cannot be coerced to a * a valid unsigned integer representation. * @retval ERANGE If value coercion would overflow (or underflow) * uint32_t. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_getvar_uint32(device_t dev, const char *name, uint32_t *value) { return (bhnd_nvram_getvar_uint(dev, name, value, sizeof(*value))); } /** * Read an NVRAM variable's signed integer value. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] value On success, the requested value will be written * to this pointer. * @param width The output integer type width (1, 2, or * 4 bytes). * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval EFTYPE If the variable data cannot be coerced to a * a valid integer representation. * @retval ERANGE If value coercion would overflow (or underflow) an * signed representation of the given @p width. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_getvar_int(device_t dev, const char *name, void *value, int width) { bhnd_nvram_type type; size_t len; switch (width) { case 1: type = BHND_NVRAM_TYPE_INT8; break; case 2: type = BHND_NVRAM_TYPE_INT16; break; case 4: type = BHND_NVRAM_TYPE_INT32; break; default: device_printf(dev, "unsupported NVRAM integer width: %d\n", width); return (EINVAL); } len = width; return (bhnd_nvram_getvar(dev, name, value, &len, type)); } /** * Read an NVRAM variable's signed 8-bit integer value. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] value On success, the requested value will be written * to this pointer. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval EFTYPE If the variable data cannot be coerced to a * a valid integer representation. * @retval ERANGE If value coercion would overflow (or underflow) int8_t. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_getvar_int8(device_t dev, const char *name, int8_t *value) { return (bhnd_nvram_getvar_int(dev, name, value, sizeof(*value))); } /** * Read an NVRAM variable's signed 16-bit integer value. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] value On success, the requested value will be written * to this pointer. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval EFTYPE If the variable data cannot be coerced to a * a valid integer representation. * @retval ERANGE If value coercion would overflow (or underflow) * int16_t. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_getvar_int16(device_t dev, const char *name, int16_t *value) { return (bhnd_nvram_getvar_int(dev, name, value, sizeof(*value))); } /** * Read an NVRAM variable's signed 32-bit integer value. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] value On success, the requested value will be written * to this pointer. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval EFTYPE If the variable data cannot be coerced to a * a valid integer representation. * @retval ERANGE If value coercion would overflow (or underflow) * int32_t. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_getvar_int32(device_t dev, const char *name, int32_t *value) { return (bhnd_nvram_getvar_int(dev, name, value, sizeof(*value))); } /** * Read an NVRAM variable's array value. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] buf A buffer large enough to hold @p size bytes. * On success, the requested value will be written * to this buffer. * @param[in,out] size The required number of bytes to write to * @p buf. * @param type The desired array element data representation. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval ENXIO If less than @p size bytes are available. * @retval ENOMEM If a buffer of @p size is too small to hold the * requested value. * @retval EFTYPE If the variable data cannot be coerced to a * a valid instance of @p type. * @retval ERANGE If value coercion would overflow (or underflow) a * representation of @p type. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_getvar_array(device_t dev, const char *name, void *buf, size_t size, bhnd_nvram_type type) { size_t nbytes; int error; /* Attempt read */ nbytes = size; if ((error = bhnd_nvram_getvar(dev, name, buf, &nbytes, type))) return (error); /* Verify that the expected number of bytes were fetched */ if (nbytes < size) return (ENXIO); return (0); } /** * Initialize a service provider registry. * * @param bsr The service registry to initialize. * * @retval 0 success * @retval non-zero if an error occurs initializing the service registry, * a regular unix error code will be returned. */ int bhnd_service_registry_init(struct bhnd_service_registry *bsr) { STAILQ_INIT(&bsr->entries); mtx_init(&bsr->lock, "bhnd_service_registry lock", NULL, MTX_DEF); return (0); } /** * Release all resources held by @p bsr. * * @param bsr A service registry instance previously successfully * initialized via bhnd_service_registry_init(). * * @retval 0 success * @retval EBUSY if active references to service providers registered * with @p bsr exist. */ int bhnd_service_registry_fini(struct bhnd_service_registry *bsr) { struct bhnd_service_entry *entry, *enext; /* Remove everthing we can */ mtx_lock(&bsr->lock); STAILQ_FOREACH_SAFE(entry, &bsr->entries, link, enext) { if (entry->refs > 0) continue; STAILQ_REMOVE(&bsr->entries, entry, bhnd_service_entry, link); free(entry, M_BHND); } if (!STAILQ_EMPTY(&bsr->entries)) { mtx_unlock(&bsr->lock); return (EBUSY); } mtx_unlock(&bsr->lock); mtx_destroy(&bsr->lock); return (0); } /** * Register a @p provider for the given @p service. * * @param bsr Service registry to be modified. * @param provider Service provider to register. * @param service Service for which @p provider will be registered. * @param flags Service provider flags (see BHND_SPF_*). * * @retval 0 success * @retval EEXIST if an entry for @p service already exists. * @retval EINVAL if @p service is BHND_SERVICE_ANY. * @retval non-zero if registering @p provider otherwise fails, a regular * unix error code will be returned. */ int bhnd_service_registry_add(struct bhnd_service_registry *bsr, device_t provider, bhnd_service_t service, uint32_t flags) { struct bhnd_service_entry *entry; if (service == BHND_SERVICE_ANY) return (EINVAL); mtx_lock(&bsr->lock); /* Is a service provider already registered? */ STAILQ_FOREACH(entry, &bsr->entries, link) { if (entry->service == service) { mtx_unlock(&bsr->lock); return (EEXIST); } } /* Initialize and insert our new entry */ entry = malloc(sizeof(*entry), M_BHND, M_NOWAIT); if (entry == NULL) { mtx_unlock(&bsr->lock); return (ENOMEM); } entry->provider = provider; entry->service = service; entry->flags = flags; refcount_init(&entry->refs, 0); STAILQ_INSERT_HEAD(&bsr->entries, entry, link); mtx_unlock(&bsr->lock); return (0); } /** * Free an unreferenced registry entry. * * @param entry The entry to be deallocated. */ static void bhnd_service_registry_free_entry(struct bhnd_service_entry *entry) { KASSERT(entry->refs == 0, ("provider has active references")); free(entry, M_BHND); } /** * Attempt to remove the @p service provider registration for @p provider. * * @param bsr The service registry to be modified. * @param provider The service provider to be deregistered. * @param service The service for which @p provider will be deregistered, * or BHND_SERVICE_ANY to remove all service * registrations for @p provider. * * @retval 0 success * @retval EBUSY if active references to @p provider exist; @see * bhnd_service_registry_retain() and * bhnd_service_registry_release(). */ int bhnd_service_registry_remove(struct bhnd_service_registry *bsr, device_t provider, bhnd_service_t service) { struct bhnd_service_entry *entry, *enext; mtx_lock(&bsr->lock); #define BHND_PROV_MATCH(_e) \ ((_e)->provider == provider && \ (service == BHND_SERVICE_ANY || (_e)->service == service)) /* Validate matching provider entries before making any * modifications */ STAILQ_FOREACH(entry, &bsr->entries, link) { /* Skip non-matching entries */ if (!BHND_PROV_MATCH(entry)) continue; /* Entry is in use? */ if (entry->refs > 0) { mtx_unlock(&bsr->lock); return (EBUSY); } } /* We can now safely remove matching entries */ STAILQ_FOREACH_SAFE(entry, &bsr->entries, link, enext) { /* Skip non-matching entries */ if (!BHND_PROV_MATCH(entry)) continue; /* Remove from list */ STAILQ_REMOVE(&bsr->entries, entry, bhnd_service_entry, link); /* Free provider entry */ bhnd_service_registry_free_entry(entry); } #undef BHND_PROV_MATCH mtx_unlock(&bsr->lock); return (0); } /** * Retain and return a reference to a registered @p service provider, if any. * * @param bsr The service registry to be queried. * @param service The service for which a provider should be returned. * * On success, the caller assumes ownership the returned provider, and * is responsible for releasing this reference via * bhnd_service_registry_release(). * * @retval device_t success * @retval NULL if no provider is registered for @p service. */ device_t bhnd_service_registry_retain(struct bhnd_service_registry *bsr, bhnd_service_t service) { struct bhnd_service_entry *entry; mtx_lock(&bsr->lock); STAILQ_FOREACH(entry, &bsr->entries, link) { if (entry->service != service) continue; /* With a live refcount, entry is gauranteed to remain alive * after we release our lock */ refcount_acquire(&entry->refs); mtx_unlock(&bsr->lock); return (entry->provider); } mtx_unlock(&bsr->lock); /* Not found */ return (NULL); } /** * Release a reference to a service provider previously returned by * bhnd_service_registry_retain(). * * If this is the last reference to an inherited service provider registration * (@see BHND_SPF_INHERITED), the registration will also be removed, and * true will be returned. * * @param bsr The service registry from which @p provider * was returned. * @param provider The provider to be released. * @param service The service for which @p provider was previously * retained. * @retval true The inherited service provider registration was removed; * the caller should release its own reference to the * provider. * @retval false The service provider was not inherited, or active * references to the provider remain. */ bool bhnd_service_registry_release(struct bhnd_service_registry *bsr, device_t provider, bhnd_service_t service) { struct bhnd_service_entry *entry; /* Exclusive lock, as we need to prevent any new references to the * entry from being taken if it's to be removed */ mtx_lock(&bsr->lock); STAILQ_FOREACH(entry, &bsr->entries, link) { bool removed; if (entry->provider != provider) continue; if (entry->service != service) continue; if (refcount_release(&entry->refs) && (entry->flags & BHND_SPF_INHERITED)) { /* If an inherited entry is no longer actively * referenced, remove the local registration and inform * the caller. */ STAILQ_REMOVE(&bsr->entries, entry, bhnd_service_entry, link); bhnd_service_registry_free_entry(entry); removed = true; } else { removed = false; } mtx_unlock(&bsr->lock); return (removed); } /* Caller owns a reference, but no such provider is registered? */ panic("invalid service provider reference"); } /** * Using the bhnd(4) bus-level core information and a custom core name, * populate @p dev's device description. * * @param dev A bhnd-bus attached device. * @param dev_name The core's name (e.g. "SDIO Device Core") */ void bhnd_set_custom_core_desc(device_t dev, const char *dev_name) { const char *vendor_name; char *desc; vendor_name = bhnd_get_vendor_name(dev); asprintf(&desc, M_BHND, "%s %s, rev %hhu", vendor_name, dev_name, bhnd_get_hwrev(dev)); if (desc != NULL) { device_set_desc_copy(dev, desc); free(desc, M_BHND); } else { device_set_desc(dev, dev_name); } } /** * Using the bhnd(4) bus-level core information, populate @p dev's device * description. * * @param dev A bhnd-bus attached device. */ void bhnd_set_default_core_desc(device_t dev) { bhnd_set_custom_core_desc(dev, bhnd_get_device_name(dev)); } /** * Using the bhnd @p chip_id, populate the bhnd(4) bus @p dev's device * description. * * @param dev A bhnd-bus attached device. */ void bhnd_set_default_bus_desc(device_t dev, const struct bhnd_chipid *chip_id) { const char *bus_name; char *desc; char chip_name[BHND_CHIPID_MAX_NAMELEN]; /* Determine chip type's bus name */ switch (chip_id->chip_type) { case BHND_CHIPTYPE_SIBA: bus_name = "SIBA bus"; break; case BHND_CHIPTYPE_BCMA: case BHND_CHIPTYPE_BCMA_ALT: bus_name = "BCMA bus"; break; case BHND_CHIPTYPE_UBUS: bus_name = "UBUS bus"; break; default: bus_name = "Unknown Type"; break; } /* Format chip name */ bhnd_format_chip_id(chip_name, sizeof(chip_name), chip_id->chip_id); /* Format and set device description */ asprintf(&desc, M_BHND, "%s %s", chip_name, bus_name); if (desc != NULL) { device_set_desc_copy(dev, desc); free(desc, M_BHND); } else { device_set_desc(dev, bus_name); } } /** * Helper function for implementing BHND_BUS_REGISTER_PROVIDER(). * * This implementation delegates the request to the BHND_BUS_REGISTER_PROVIDER() * method on the parent of @p dev. If no parent exists, the implementation * will return an error. */ int bhnd_bus_generic_register_provider(device_t dev, device_t child, device_t provider, bhnd_service_t service) { device_t parent = device_get_parent(dev); if (parent != NULL) { return (BHND_BUS_REGISTER_PROVIDER(parent, child, provider, service)); } return (ENXIO); } /** * Helper function for implementing BHND_BUS_DEREGISTER_PROVIDER(). * * This implementation delegates the request to the * BHND_BUS_DEREGISTER_PROVIDER() method on the parent of @p dev. If no parent * exists, the implementation will panic. */ int bhnd_bus_generic_deregister_provider(device_t dev, device_t child, device_t provider, bhnd_service_t service) { device_t parent = device_get_parent(dev); if (parent != NULL) { return (BHND_BUS_DEREGISTER_PROVIDER(parent, child, provider, service)); } panic("missing BHND_BUS_DEREGISTER_PROVIDER()"); } /** * Helper function for implementing BHND_BUS_RETAIN_PROVIDER(). * * This implementation delegates the request to the * BHND_BUS_DEREGISTER_PROVIDER() method on the parent of @p dev. If no parent * exists, the implementation will return NULL. */ device_t bhnd_bus_generic_retain_provider(device_t dev, device_t child, bhnd_service_t service) { device_t parent = device_get_parent(dev); if (parent != NULL) { return (BHND_BUS_RETAIN_PROVIDER(parent, child, service)); } return (NULL); } /** * Helper function for implementing BHND_BUS_RELEASE_PROVIDER(). * * This implementation delegates the request to the * BHND_BUS_DEREGISTER_PROVIDER() method on the parent of @p dev. If no parent * exists, the implementation will panic. */ void bhnd_bus_generic_release_provider(device_t dev, device_t child, device_t provider, bhnd_service_t service) { device_t parent = device_get_parent(dev); if (parent != NULL) { return (BHND_BUS_RELEASE_PROVIDER(parent, child, provider, service)); } panic("missing BHND_BUS_RELEASE_PROVIDER()"); } /** * Helper function for implementing BHND_BUS_REGISTER_PROVIDER(). * * This implementation uses the bhnd_service_registry_add() function to * do most of the work. It calls BHND_BUS_GET_SERVICE_REGISTRY() to find * a suitable service registry to edit. */ int bhnd_bus_generic_sr_register_provider(device_t dev, device_t child, device_t provider, bhnd_service_t service) { struct bhnd_service_registry *bsr; bsr = BHND_BUS_GET_SERVICE_REGISTRY(dev, child); KASSERT(bsr != NULL, ("NULL service registry")); return (bhnd_service_registry_add(bsr, provider, service, 0)); } /** * Helper function for implementing BHND_BUS_DEREGISTER_PROVIDER(). * * This implementation uses the bhnd_service_registry_remove() function to * do most of the work. It calls BHND_BUS_GET_SERVICE_REGISTRY() to find * a suitable service registry to edit. */ int bhnd_bus_generic_sr_deregister_provider(device_t dev, device_t child, device_t provider, bhnd_service_t service) { struct bhnd_service_registry *bsr; bsr = BHND_BUS_GET_SERVICE_REGISTRY(dev, child); KASSERT(bsr != NULL, ("NULL service registry")); return (bhnd_service_registry_remove(bsr, provider, service)); } /** * Helper function for implementing BHND_BUS_RETAIN_PROVIDER(). * * This implementation uses the bhnd_service_registry_retain() function to * do most of the work. It calls BHND_BUS_GET_SERVICE_REGISTRY() to find * a suitable service registry. * * If a local provider for the service is not available, and a parent device is * available, this implementation will attempt to fetch and locally register * a service provider reference from the parent of @p dev. */ device_t bhnd_bus_generic_sr_retain_provider(device_t dev, device_t child, bhnd_service_t service) { struct bhnd_service_registry *bsr; device_t parent, provider; int error; bsr = BHND_BUS_GET_SERVICE_REGISTRY(dev, child); KASSERT(bsr != NULL, ("NULL service registry")); /* * Attempt to fetch a service provider reference from either the local * service registry, or if not found, from our parent. * * If we fetch a provider from our parent, we register the provider * with the local service registry to prevent conflicting local * registrations from being added. */ while (1) { /* Check the local service registry first */ provider = bhnd_service_registry_retain(bsr, service); if (provider != NULL) return (provider); /* Otherwise, try to delegate to our parent (if any) */ if ((parent = device_get_parent(dev)) == NULL) return (NULL); provider = BHND_BUS_RETAIN_PROVIDER(parent, dev, service); if (provider == NULL) return (NULL); /* Register the inherited service registration with the local * registry */ error = bhnd_service_registry_add(bsr, provider, service, BHND_SPF_INHERITED); if (error) { BHND_BUS_RELEASE_PROVIDER(parent, dev, provider, service); if (error == EEXIST) { /* A valid service provider was registered * concurrently; retry fetching from the local * registry */ continue; } device_printf(dev, "failed to register service " "provider: %d\n", error); return (NULL); } } } /** * Helper function for implementing BHND_BUS_RELEASE_PROVIDER(). * * This implementation uses the bhnd_service_registry_release() function to * do most of the work. It calls BHND_BUS_GET_SERVICE_REGISTRY() to find * a suitable service registry. */ void bhnd_bus_generic_sr_release_provider(device_t dev, device_t child, device_t provider, bhnd_service_t service) { struct bhnd_service_registry *bsr; bsr = BHND_BUS_GET_SERVICE_REGISTRY(dev, child); KASSERT(bsr != NULL, ("NULL service registry")); /* Release the provider reference; if the refcount hits zero on an * inherited reference, true will be returned, and we need to drop * our own bus reference to the provider */ if (!bhnd_service_registry_release(bsr, provider, service)) return; /* Drop our reference to the borrowed provider */ BHND_BUS_RELEASE_PROVIDER(device_get_parent(dev), dev, provider, service); } /** * Helper function for implementing BHND_BUS_IS_HW_DISABLED(). * * If a parent device is available, this implementation delegates the * request to the BHND_BUS_IS_HW_DISABLED() method on the parent of @p dev. * * If no parent device is available (i.e. on a the bus root), the hardware * is assumed to be usable and false is returned. */ bool bhnd_bus_generic_is_hw_disabled(device_t dev, device_t child) { if (device_get_parent(dev) != NULL) return (BHND_BUS_IS_HW_DISABLED(device_get_parent(dev), child)); return (false); } /** * Helper function for implementing BHND_BUS_GET_CHIPID(). * * This implementation delegates the request to the BHND_BUS_GET_CHIPID() * method on the parent of @p dev. If no parent exists, the implementation * will panic. */ const struct bhnd_chipid * bhnd_bus_generic_get_chipid(device_t dev, device_t child) { if (device_get_parent(dev) != NULL) return (BHND_BUS_GET_CHIPID(device_get_parent(dev), child)); panic("missing BHND_BUS_GET_CHIPID()"); } +/** + * Helper function for implementing BHND_BUS_GET_DMA_TRANSLATION(). + * + * If a parent device is available, this implementation delegates the + * request to the BHND_BUS_GET_DMA_TRANSLATION() method on the parent of @p dev. + * + * If no parent device is available, this implementation will panic. + */ +int +bhnd_bus_generic_get_dma_translation(device_t dev, device_t child, u_int width, + uint32_t flags, bus_dma_tag_t *dmat, + struct bhnd_dma_translation *translation) +{ + if (device_get_parent(dev) != NULL) { + return (BHND_BUS_GET_DMA_TRANSLATION(device_get_parent(dev), + child, width, flags, dmat, translation)); + } + + panic("missing BHND_BUS_GET_DMA_TRANSLATION()"); +} + /* nvram board_info population macros for bhnd_bus_generic_read_board_info() */ #define BHND_GV(_dest, _name) \ bhnd_nvram_getvar_uint(child, BHND_NVAR_ ## _name, &_dest, \ sizeof(_dest)) #define REQ_BHND_GV(_dest, _name) do { \ if ((error = BHND_GV(_dest, _name))) { \ device_printf(dev, \ "error reading " __STRING(_name) ": %d\n", error); \ return (error); \ } \ } while(0) #define OPT_BHND_GV(_dest, _name, _default) do { \ if ((error = BHND_GV(_dest, _name))) { \ if (error != ENOENT) { \ device_printf(dev, \ "error reading " \ __STRING(_name) ": %d\n", error); \ return (error); \ } \ _dest = _default; \ } \ } while(0) /** * Helper function for implementing BHND_BUS_READ_BOARDINFO(). * * This implementation populates @p info with information from NVRAM, * defaulting board_vendor and board_type fields to 0 if the * requested variables cannot be found. * * This behavior is correct for most SoCs, but must be overridden on * bridged (PCI, PCMCIA, etc) devices to produce a complete bhnd_board_info * result. */ int bhnd_bus_generic_read_board_info(device_t dev, device_t child, struct bhnd_board_info *info) { int error; OPT_BHND_GV(info->board_vendor, BOARDVENDOR, 0); OPT_BHND_GV(info->board_type, BOARDTYPE, 0); /* srom >= 2 */ REQ_BHND_GV(info->board_rev, BOARDREV); OPT_BHND_GV(info->board_srom_rev,SROMREV, 0); /* missing in some SoC NVRAM */ REQ_BHND_GV(info->board_flags, BOARDFLAGS); OPT_BHND_GV(info->board_flags2, BOARDFLAGS2, 0); /* srom >= 4 */ OPT_BHND_GV(info->board_flags3, BOARDFLAGS3, 0); /* srom >= 11 */ return (0); } #undef BHND_GV #undef BHND_GV_REQ #undef BHND_GV_OPT /** * Helper function for implementing BHND_BUS_GET_NVRAM_VAR(). * * This implementation searches @p dev for a usable NVRAM child device. * * If no usable child device is found on @p dev, the request is delegated to * the BHND_BUS_GET_NVRAM_VAR() method on the parent of @p dev. */ int bhnd_bus_generic_get_nvram_var(device_t dev, device_t child, const char *name, void *buf, size_t *size, bhnd_nvram_type type) { device_t nvram; device_t parent; /* Make sure we're holding Giant for newbus */ GIANT_REQUIRED; /* Look for a directly-attached NVRAM child */ if ((nvram = device_find_child(dev, "bhnd_nvram", -1)) != NULL) return BHND_NVRAM_GETVAR(nvram, name, buf, size, type); /* Try to delegate to parent */ if ((parent = device_get_parent(dev)) == NULL) return (ENODEV); return (BHND_BUS_GET_NVRAM_VAR(device_get_parent(dev), child, name, buf, size, type)); } /** * Helper function for implementing BHND_BUS_ALLOC_RESOURCE(). * * This implementation of BHND_BUS_ALLOC_RESOURCE() delegates allocation * of the underlying resource to BUS_ALLOC_RESOURCE(), and activation * to @p dev's BHND_BUS_ACTIVATE_RESOURCE(). */ struct bhnd_resource * bhnd_bus_generic_alloc_resource(device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct bhnd_resource *br; struct resource *res; int error; br = NULL; res = NULL; /* Allocate the real bus resource (without activating it) */ res = BUS_ALLOC_RESOURCE(dev, child, type, rid, start, end, count, (flags & ~RF_ACTIVE)); if (res == NULL) return (NULL); /* Allocate our bhnd resource wrapper. */ br = malloc(sizeof(struct bhnd_resource), M_BHND, M_NOWAIT); if (br == NULL) goto failed; br->direct = false; br->res = res; /* Attempt activation */ if (flags & RF_ACTIVE) { error = BHND_BUS_ACTIVATE_RESOURCE(dev, child, type, *rid, br); if (error) goto failed; } return (br); failed: if (res != NULL) BUS_RELEASE_RESOURCE(dev, child, type, *rid, res); free(br, M_BHND); return (NULL); } /** * Helper function for implementing BHND_BUS_RELEASE_RESOURCE(). * * This implementation of BHND_BUS_RELEASE_RESOURCE() delegates release of * the backing resource to BUS_RELEASE_RESOURCE(). */ int bhnd_bus_generic_release_resource(device_t dev, device_t child, int type, int rid, struct bhnd_resource *r) { int error; if ((error = BUS_RELEASE_RESOURCE(dev, child, type, rid, r->res))) return (error); free(r, M_BHND); return (0); } /** * Helper function for implementing BHND_BUS_ACTIVATE_RESOURCE(). * * This implementation of BHND_BUS_ACTIVATE_RESOURCE() first calls the * BHND_BUS_ACTIVATE_RESOURCE() method of the parent of @p dev. * * If this fails, and if @p dev is the direct parent of @p child, standard * resource activation is attempted via bus_activate_resource(). This enables * direct use of the bhnd(4) resource APIs on devices that may not be attached * to a parent bhnd bus or bridge. */ int bhnd_bus_generic_activate_resource(device_t dev, device_t child, int type, int rid, struct bhnd_resource *r) { int error; bool passthrough; passthrough = (device_get_parent(child) != dev); /* Try to delegate to the parent */ if (device_get_parent(dev) != NULL) { error = BHND_BUS_ACTIVATE_RESOURCE(device_get_parent(dev), child, type, rid, r); } else { error = ENODEV; } /* If bhnd(4) activation has failed and we're the child's direct * parent, try falling back on standard resource activation. */ if (error && !passthrough) { error = bus_activate_resource(child, type, rid, r->res); if (!error) r->direct = true; } return (error); } /** * Helper function for implementing BHND_BUS_DEACTIVATE_RESOURCE(). * * This implementation of BHND_BUS_ACTIVATE_RESOURCE() simply calls the * BHND_BUS_ACTIVATE_RESOURCE() method of the parent of @p dev. */ int bhnd_bus_generic_deactivate_resource(device_t dev, device_t child, int type, int rid, struct bhnd_resource *r) { if (device_get_parent(dev) != NULL) return (BHND_BUS_DEACTIVATE_RESOURCE(device_get_parent(dev), child, type, rid, r)); 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: head/sys/dev/bhnd/bhndb/bhndb.c =================================================================== --- head/sys/dev/bhnd/bhndb/bhndb.c (revision 326079) +++ head/sys/dev/bhnd/bhndb/bhndb.c (revision 326080) @@ -1,2171 +1,2252 @@ /*- * Copyright (c) 2015-2016 Landon Fuller * 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: * 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. */ #include __FBSDID("$FreeBSD$"); /* * Abstract BHND Bridge Device Driver * * Provides generic support for bridging from a parent bus (such as PCI) to * a BHND-compatible bus (e.g. bcma or siba). */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bhnd_chipc_if.h" #include "bhnd_nvram_if.h" #include "bhndbvar.h" #include "bhndb_bus_if.h" #include "bhndb_hwdata.h" #include "bhndb_private.h" /* Debugging flags */ static u_long bhndb_debug = 0; TUNABLE_ULONG("hw.bhndb.debug", &bhndb_debug); enum { BHNDB_DEBUG_PRIO = 1 << 0, }; #define BHNDB_DEBUG(_type) (BHNDB_DEBUG_ ## _type & bhndb_debug) static bool bhndb_hw_matches(struct bhndb_softc *sc, struct bhnd_core_info *cores, u_int ncores, const struct bhndb_hw *hw); static int bhndb_init_region_cfg(struct bhndb_softc *sc, bhnd_erom_t *erom, struct bhndb_resources *r, struct bhnd_core_info *cores, u_int ncores, const struct bhndb_hw_priority *table); static int bhndb_find_hwspec(struct bhndb_softc *sc, struct bhnd_core_info *cores, u_int ncores, const struct bhndb_hw **hw); bhndb_addrspace bhndb_get_addrspace(struct bhndb_softc *sc, device_t child); static struct rman *bhndb_get_rman(struct bhndb_softc *sc, device_t child, int type); static int bhndb_init_child_resource(struct resource *r, struct resource *parent, bhnd_size_t offset, bhnd_size_t size); static int bhndb_activate_static_region( struct bhndb_softc *sc, struct bhndb_region *region, device_t child, int type, int rid, struct resource *r); static int bhndb_try_activate_resource( struct bhndb_softc *sc, device_t child, int type, int rid, struct resource *r, bool *indirect); static inline struct bhndb_dw_alloc *bhndb_io_resource(struct bhndb_softc *sc, bus_addr_t addr, bus_size_t size, bus_size_t *offset); /** * Default bhndb(4) implementation of DEVICE_PROBE(). * * This function provides the default bhndb implementation of DEVICE_PROBE(), * and is compatible with bhndb(4) bridges attached via bhndb_attach_bridge(). */ int bhndb_generic_probe(device_t dev) { return (BUS_PROBE_NOWILDCARD); } static void bhndb_probe_nomatch(device_t dev, device_t child) { const char *name; name = device_get_name(child); if (name == NULL) name = "unknown device"; device_printf(dev, "<%s> (no driver attached)\n", name); } static int bhndb_print_child(device_t dev, device_t child) { struct bhndb_softc *sc; struct resource_list *rl; int retval = 0; sc = device_get_softc(dev); retval += bus_print_child_header(dev, child); rl = BUS_GET_RESOURCE_LIST(dev, child); if (rl != NULL) { retval += resource_list_print_type(rl, "mem", SYS_RES_MEMORY, "%#jx"); retval += resource_list_print_type(rl, "irq", SYS_RES_IRQ, "%jd"); } retval += bus_print_child_domain(dev, child); retval += bus_print_child_footer(dev, child); return (retval); } static int bhndb_child_pnpinfo_str(device_t bus, device_t child, char *buf, size_t buflen) { *buf = '\0'; return (0); } static int bhndb_child_location_str(device_t dev, device_t child, char *buf, size_t buflen) { struct bhndb_softc *sc; sc = device_get_softc(dev); snprintf(buf, buflen, "base=0x%llx", (unsigned long long) sc->chipid.enum_addr); return (0); } /** * Return true if @p cores matches the @p hw specification. * * @param sc BHNDB device state. * @param cores A device table to match against. * @param ncores The number of cores in @p cores. * @param hw The hardware description to be matched against. */ static bool bhndb_hw_matches(struct bhndb_softc *sc, struct bhnd_core_info *cores, u_int ncores, const struct bhndb_hw *hw) { for (u_int i = 0; i < hw->num_hw_reqs; i++) { const struct bhnd_core_match *match; bool found; match = &hw->hw_reqs[i]; found = false; for (u_int d = 0; d < ncores; d++) { struct bhnd_core_info *core = &cores[d]; if (BHNDB_IS_CORE_DISABLED(sc->dev, sc->bus_dev, core)) continue; if (!bhnd_core_matches(core, match)) continue; found = true; break; } if (!found) return (false); } return (true); } /** * Initialize the region maps and priority configuration in @p br using * the priority @p table and the set of cores enumerated by @p erom. * * @param sc The bhndb device state. * @param br The resource state to be configured. * @param erom EROM parser used to enumerate @p cores. * @param cores All cores enumerated on the bridged bhnd bus. * @param ncores The length of @p cores. * @param table Hardware priority table to be used to determine the relative * priorities of per-core port resources. */ static int bhndb_init_region_cfg(struct bhndb_softc *sc, bhnd_erom_t *erom, struct bhndb_resources *br, struct bhnd_core_info *cores, u_int ncores, const struct bhndb_hw_priority *table) { const struct bhndb_hw_priority *hp; bhnd_addr_t addr; bhnd_size_t size; size_t prio_low, prio_default, prio_high; int error; /* The number of port regions per priority band that must be accessible * via dynamic register windows */ prio_low = 0; prio_default = 0; prio_high = 0; /* * Register bridge regions covering all statically mapped ports. */ for (u_int i = 0; i < ncores; i++) { const struct bhndb_regwin *regw; struct bhnd_core_info *core; struct bhnd_core_match md; core = &cores[i]; md = bhnd_core_get_match_desc(core); for (regw = br->cfg->register_windows; regw->win_type != BHNDB_REGWIN_T_INVALID; regw++) { /* Only core windows are supported */ if (regw->win_type != BHNDB_REGWIN_T_CORE) continue; /* Skip non-matching cores. */ if (!bhndb_regwin_match_core(regw, core)) continue; /* Fetch the base address of the mapped port */ error = bhnd_erom_lookup_core_addr(erom, &md, regw->d.core.port_type, regw->d.core.port, regw->d.core.region, NULL, &addr, &size); if (error) { /* Skip non-applicable register windows */ if (error == ENOENT) continue; return (error); } /* * Always defer to the register window's size. * * If the port size is smaller than the window size, * this ensures that we fully utilize register windows * larger than the referenced port. * * If the port size is larger than the window size, this * ensures that we do not directly map the allocations * within the region to a too-small window. */ size = regw->win_size; /* * Add to the bus region list. * * The window priority for a statically mapped * region is always HIGH. */ error = bhndb_add_resource_region(br, addr, size, BHNDB_PRIORITY_HIGH, regw); if (error) return (error); } } /* * Perform priority accounting and register bridge regions for all * ports defined in the priority table */ for (u_int i = 0; i < ncores; i++) { struct bhndb_region *region; struct bhnd_core_info *core; struct bhnd_core_match md; core = &cores[i]; md = bhnd_core_get_match_desc(core); /* * Skip priority accounting for cores that ... */ /* ... do not require bridge resources */ if (BHNDB_IS_CORE_DISABLED(sc->dev, sc->bus_dev, core)) continue; /* ... do not have a priority table entry */ hp = bhndb_hw_priority_find_core(table, core); if (hp == NULL) continue; /* ... are explicitly disabled in the priority table. */ if (hp->priority == BHNDB_PRIORITY_NONE) continue; /* Determine the number of dynamic windows required and * register their bus_region entries. */ for (u_int i = 0; i < hp->num_ports; i++) { const struct bhndb_port_priority *pp; pp = &hp->ports[i]; /* Fetch the address+size of the mapped port. */ error = bhnd_erom_lookup_core_addr(erom, &md, pp->type, pp->port, pp->region, NULL, &addr, &size); if (error) { /* Skip ports not defined on this device */ if (error == ENOENT) continue; return (error); } /* Skip ports with an existing static mapping */ region = bhndb_find_resource_region(br, addr, size); if (region != NULL && region->static_regwin != NULL) continue; /* Define a dynamic region for this port */ error = bhndb_add_resource_region(br, addr, size, pp->priority, NULL); if (error) return (error); /* Update port mapping counts */ switch (pp->priority) { case BHNDB_PRIORITY_NONE: break; case BHNDB_PRIORITY_LOW: prio_low++; break; case BHNDB_PRIORITY_DEFAULT: prio_default++; break; case BHNDB_PRIORITY_HIGH: prio_high++; break; } } } /* Determine the minimum priority at which we'll allocate direct * register windows from our dynamic pool */ size_t prio_total = prio_low + prio_default + prio_high; if (prio_total <= br->dwa_count) { /* low+default+high priority regions get windows */ br->min_prio = BHNDB_PRIORITY_LOW; } else if (prio_default + prio_high <= br->dwa_count) { /* default+high priority regions get windows */ br->min_prio = BHNDB_PRIORITY_DEFAULT; } else { /* high priority regions get windows */ br->min_prio = BHNDB_PRIORITY_HIGH; } if (BHNDB_DEBUG(PRIO)) { struct bhndb_region *region; const char *direct_msg, *type_msg; bhndb_priority_t prio, prio_min; prio_min = br->min_prio; device_printf(sc->dev, "min_prio: %d\n", prio_min); STAILQ_FOREACH(region, &br->bus_regions, link) { prio = region->priority; direct_msg = prio >= prio_min ? "direct" : "indirect"; type_msg = region->static_regwin ? "static" : "dynamic"; device_printf(sc->dev, "region 0x%llx+0x%llx priority " "%u %s/%s\n", (unsigned long long) region->addr, (unsigned long long) region->size, region->priority, direct_msg, type_msg); } } return (0); } /** * Find a hardware specification for @p dev. * * @param sc The bhndb device state. * @param cores All cores enumerated on the bridged bhnd bus. * @param ncores The length of @p cores. * @param[out] hw On success, the matched hardware specification. * with @p dev. * * @retval 0 success * @retval non-zero if an error occurs fetching device info for comparison. */ static int bhndb_find_hwspec(struct bhndb_softc *sc, struct bhnd_core_info *cores, u_int ncores, const struct bhndb_hw **hw) { const struct bhndb_hw *next, *hw_table; /* Search for the first matching hardware config. */ hw_table = BHNDB_BUS_GET_HARDWARE_TABLE(sc->parent_dev, sc->dev); for (next = hw_table; next->hw_reqs != NULL; next++) { if (!bhndb_hw_matches(sc, cores, ncores, next)) continue; /* Found */ *hw = next; return (0); } return (ENOENT); } /** * Helper function that must be called by subclass bhndb(4) drivers * when implementing DEVICE_ATTACH() before calling any bhnd(4) or bhndb(4) * APIs on the bridge device. * * This function will add a bridged bhnd(4) child device with a device order of * BHND_PROBE_BUS. Any subclass bhndb(4) driver may use the BHND_PROBE_* * priority bands to add additional devices that will be attached in * their preferred order relative to the bridged bhnd(4) bus. * * @param dev The bridge device to attach. * @param cid The bridged device's chip identification. * @param cores The bridged device's core table. * @param ncores The number of cores in @p cores. * @param bridge_core Core info for the bhnd(4) core serving as the host * bridge. * @param erom_class An erom parser class that may be used to parse * the bridged device's device enumeration table. */ int bhndb_attach(device_t dev, struct bhnd_chipid *cid, struct bhnd_core_info *cores, u_int ncores, struct bhnd_core_info *bridge_core, bhnd_erom_class_t *erom_class) { struct bhndb_devinfo *dinfo; struct bhndb_softc *sc; const struct bhndb_hw *hw; const struct bhndb_hwcfg *hwcfg; const struct bhndb_hw_priority *hwprio; struct bhnd_erom_io *eio; bhnd_erom_t *erom; int error; sc = device_get_softc(dev); sc->dev = dev; sc->parent_dev = device_get_parent(dev); sc->bridge_core = *bridge_core; sc->chipid = *cid; if ((error = bhnd_service_registry_init(&sc->services))) return (error); BHNDB_LOCK_INIT(sc); erom = NULL; /* Find a matching bridge hardware configuration */ if ((error = bhndb_find_hwspec(sc, cores, ncores, &hw))) { device_printf(sc->dev, "unable to identify device, " " using generic bridge resource definitions\n"); hwcfg = BHNDB_BUS_GET_GENERIC_HWCFG(sc->parent_dev, dev); hw = NULL; } else { hwcfg = hw->cfg; } if (hw != NULL && (bootverbose || BHNDB_DEBUG(PRIO))) { device_printf(sc->dev, "%s resource configuration\n", hw->name); } /* Allocate bridge resource state using the discovered hardware * configuration */ sc->bus_res = bhndb_alloc_resources(sc->dev, sc->parent_dev, hwcfg); if (sc->bus_res == NULL) { device_printf(sc->dev, "failed to allocate bridge resource " "state\n"); error = ENOMEM; goto failed; } /* Add our bridged bus device */ sc->bus_dev = BUS_ADD_CHILD(dev, BHND_PROBE_BUS, "bhnd", -1); if (sc->bus_dev == NULL) { error = ENXIO; goto failed; } dinfo = device_get_ivars(sc->bus_dev); dinfo->addrspace = BHNDB_ADDRSPACE_BRIDGED; /* We can now use bhndb to perform bridging of SYS_RES_MEMORY resources; * we use this to instantiate an erom parser instance */ eio = bhnd_erom_iores_new(sc->bus_dev, 0); if ((erom = bhnd_erom_alloc(erom_class, cid, eio)) == NULL) { bhnd_erom_io_fini(eio); error = ENXIO; goto failed; } /* Populate our resource priority configuration */ hwprio = BHNDB_BUS_GET_HARDWARE_PRIO(sc->parent_dev, sc->dev); error = bhndb_init_region_cfg(sc, erom, sc->bus_res, cores, ncores, hwprio); if (error) { device_printf(sc->dev, "failed to initialize resource " "priority configuration: %d\n", error); goto failed; } /* Free our erom instance */ bhnd_erom_free(erom); erom = NULL; return (0); failed: BHNDB_LOCK_DESTROY(sc); if (sc->bus_res != NULL) bhndb_free_resources(sc->bus_res); if (erom != NULL) bhnd_erom_free(erom); bhnd_service_registry_fini(&sc->services); return (error); } /** * Default bhndb(4) implementation of DEVICE_DETACH(). * * This function detaches any child devices, and if successful, releases all * resources held by the bridge device. */ int bhndb_generic_detach(device_t dev) { struct bhndb_softc *sc; int error; sc = device_get_softc(dev); /* Detach children */ if ((error = bus_generic_detach(dev))) return (error); /* Clean up our service registry */ if ((error = bhnd_service_registry_fini(&sc->services))) return (error); /* Clean up our driver state. */ bhndb_free_resources(sc->bus_res); BHNDB_LOCK_DESTROY(sc); return (0); } /** * Default bhndb(4) implementation of DEVICE_SUSPEND(). * * This function calls bus_generic_suspend() (or implements equivalent * behavior). */ int bhndb_generic_suspend(device_t dev) { return (bus_generic_suspend(dev)); } /** * Default bhndb(4) implementation of DEVICE_RESUME(). * * This function calls bus_generic_resume() (or implements equivalent * behavior). */ int bhndb_generic_resume(device_t dev) { struct bhndb_softc *sc; struct bhndb_resources *bus_res; struct bhndb_dw_alloc *dwa; int error; sc = device_get_softc(dev); bus_res = sc->bus_res; /* Guarantee that all in-use dynamic register windows are mapped to * their previously configured target address. */ BHNDB_LOCK(sc); for (size_t i = 0; i < bus_res->dwa_count; i++) { dwa = &bus_res->dw_alloc[i]; /* Skip regions that were not previously used */ if (bhndb_dw_is_free(bus_res, dwa) && dwa->target == 0x0) continue; /* Otherwise, ensure the register window is correct before * any children attempt MMIO */ error = BHNDB_SET_WINDOW_ADDR(dev, dwa->win, dwa->target); if (error) break; } BHNDB_UNLOCK(sc); /* Error restoring hardware state; children cannot be safely resumed */ if (error) { device_printf(dev, "Unable to restore hardware configuration; " "cannot resume: %d\n", error); return (error); } return (bus_generic_resume(dev)); } /** * Default implementation of BHNDB_SUSPEND_RESOURCE. */ static void bhndb_suspend_resource(device_t dev, device_t child, int type, struct resource *r) { struct bhndb_softc *sc; struct bhndb_dw_alloc *dwa; sc = device_get_softc(dev); /* Non-MMIO resources (e.g. IRQs) are handled solely by our parent */ if (type != SYS_RES_MEMORY) return; BHNDB_LOCK(sc); dwa = bhndb_dw_find_resource(sc->bus_res, r); if (dwa == NULL) { BHNDB_UNLOCK(sc); return; } if (BHNDB_DEBUG(PRIO)) device_printf(child, "suspend resource type=%d 0x%jx+0x%jx\n", type, rman_get_start(r), rman_get_size(r)); /* Release the resource's window reference */ bhndb_dw_release(sc->bus_res, dwa, r); BHNDB_UNLOCK(sc); } /** * Default implementation of BHNDB_RESUME_RESOURCE. */ static int bhndb_resume_resource(device_t dev, device_t child, int type, struct resource *r) { struct bhndb_softc *sc; sc = device_get_softc(dev); /* Non-MMIO resources (e.g. IRQs) are handled solely by our parent */ if (type != SYS_RES_MEMORY) return (0); /* Inactive resources don't require reallocation of bridge resources */ if (!(rman_get_flags(r) & RF_ACTIVE)) return (0); if (BHNDB_DEBUG(PRIO)) device_printf(child, "resume resource type=%d 0x%jx+0x%jx\n", type, rman_get_start(r), rman_get_size(r)); return (bhndb_try_activate_resource(sc, rman_get_device(r), type, rman_get_rid(r), r, NULL)); } /** * Default bhndb(4) implementation of BUS_READ_IVAR(). */ static int bhndb_read_ivar(device_t dev, device_t child, int index, uintptr_t *result) { return (ENOENT); } /** * Default bhndb(4) implementation of BUS_WRITE_IVAR(). */ static int bhndb_write_ivar(device_t dev, device_t child, int index, uintptr_t value) { return (ENOENT); } /** * Return the address space for the given @p child device. */ bhndb_addrspace bhndb_get_addrspace(struct bhndb_softc *sc, device_t child) { struct bhndb_devinfo *dinfo; device_t imd_dev; /* Find the directly attached parent of the requesting device */ imd_dev = child; while (imd_dev != NULL && device_get_parent(imd_dev) != sc->dev) imd_dev = device_get_parent(imd_dev); if (imd_dev == NULL) panic("bhndb address space request for non-child device %s\n", device_get_nameunit(child)); dinfo = device_get_ivars(imd_dev); return (dinfo->addrspace); } /** * Return the rman instance for a given resource @p type, if any. * * @param sc The bhndb device state. * @param child The requesting child. * @param type The resource type (e.g. SYS_RES_MEMORY, SYS_RES_IRQ, ...) */ static struct rman * bhndb_get_rman(struct bhndb_softc *sc, device_t child, int type) { switch (bhndb_get_addrspace(sc, child)) { case BHNDB_ADDRSPACE_NATIVE: switch (type) { case SYS_RES_MEMORY: return (&sc->bus_res->ht_mem_rman); case SYS_RES_IRQ: return (NULL); default: return (NULL); } case BHNDB_ADDRSPACE_BRIDGED: switch (type) { case SYS_RES_MEMORY: return (&sc->bus_res->br_mem_rman); case SYS_RES_IRQ: return (&sc->bus_res->br_irq_rman); default: return (NULL); } } /* Quieten gcc */ return (NULL); } /** * Default implementation of BUS_ADD_CHILD() */ static device_t bhndb_add_child(device_t dev, u_int order, const char *name, int unit) { struct bhndb_devinfo *dinfo; device_t child; child = device_add_child_ordered(dev, order, name, unit); if (child == NULL) return (NULL); dinfo = malloc(sizeof(struct bhndb_devinfo), M_BHND, M_NOWAIT); if (dinfo == NULL) { device_delete_child(dev, child); return (NULL); } dinfo->addrspace = BHNDB_ADDRSPACE_NATIVE; resource_list_init(&dinfo->resources); device_set_ivars(child, dinfo); return (child); } /** * Default implementation of BUS_CHILD_DELETED(). */ static void bhndb_child_deleted(device_t dev, device_t child) { struct bhndb_devinfo *dinfo = device_get_ivars(child); if (dinfo != NULL) { resource_list_free(&dinfo->resources); free(dinfo, M_BHND); } device_set_ivars(child, NULL); } /** * Default implementation of BHNDB_GET_CHIPID(). */ static const struct bhnd_chipid * bhndb_get_chipid(device_t dev, device_t child) { struct bhndb_softc *sc = device_get_softc(dev); return (&sc->chipid); } /** * Default implementation of BHNDB_IS_CORE_DISABLED(). */ static bool bhndb_is_core_disabled(device_t dev, device_t child, struct bhnd_core_info *core) { struct bhndb_softc *sc; sc = device_get_softc(dev); /* Try to defer to the bhndb bus parent */ if (BHNDB_BUS_IS_CORE_DISABLED(sc->parent_dev, dev, core)) return (true); /* Otherwise, we treat bridge-capable cores as unpopulated if they're * not the configured host bridge */ if (BHND_DEVCLASS_SUPPORTS_HOSTB(bhnd_core_class(core))) return (!bhnd_cores_equal(core, &sc->bridge_core)); /* Assume the core is populated */ return (false); } /** * Default bhndb(4) implementation of BHNDB_GET_HOSTB_CORE(). * * This function uses a heuristic valid on all known PCI/PCIe/PCMCIA-bridged * bhnd(4) devices. */ static int bhndb_get_hostb_core(device_t dev, device_t child, struct bhnd_core_info *core) { struct bhndb_softc *sc = device_get_softc(dev); *core = sc->bridge_core; return (0); } /** * Default bhndb(4) implementation of BHND_BUS_GET_SERVICE_REGISTRY(). */ static struct bhnd_service_registry * bhndb_get_service_registry(device_t dev, device_t child) { struct bhndb_softc *sc = device_get_softc(dev); return (&sc->services); } /** * Default bhndb(4) implementation of BUS_ALLOC_RESOURCE(). */ static struct resource * bhndb_alloc_resource(device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct bhndb_softc *sc; struct resource_list_entry *rle; struct resource *rv; struct rman *rm; int error; bool passthrough, isdefault; sc = device_get_softc(dev); passthrough = (device_get_parent(child) != dev); isdefault = RMAN_IS_DEFAULT_RANGE(start, end); rle = NULL; /* Fetch the resource manager */ rm = bhndb_get_rman(sc, child, type); if (rm == NULL) { /* Delegate to our parent device's bus; the requested * resource type isn't handled locally. */ return (BUS_ALLOC_RESOURCE(device_get_parent(sc->parent_dev), child, type, rid, start, end, count, flags)); } /* Populate defaults */ if (!passthrough && isdefault) { /* Fetch the resource list entry. */ rle = resource_list_find(BUS_GET_RESOURCE_LIST(dev, child), type, *rid); if (rle == NULL) { device_printf(dev, "default resource %#x type %d for child %s " "not found\n", *rid, type, device_get_nameunit(child)); return (NULL); } if (rle->res != NULL) { device_printf(dev, "resource entry %#x type %d for child %s is busy\n", *rid, type, device_get_nameunit(child)); return (NULL); } start = rle->start; end = rle->end; count = ulmax(count, rle->count); } /* Validate resource addresses */ if (start > end || count > ((end - start) + 1)) return (NULL); /* Make our reservation */ rv = rman_reserve_resource(rm, start, end, count, flags & ~RF_ACTIVE, child); if (rv == NULL) return (NULL); rman_set_rid(rv, *rid); /* Activate */ if (flags & RF_ACTIVE) { error = bus_activate_resource(child, type, *rid, rv); if (error) { device_printf(dev, "failed to activate entry %#x type %d for " "child %s: %d\n", *rid, type, device_get_nameunit(child), error); rman_release_resource(rv); return (NULL); } } /* Update child's resource list entry */ if (rle != NULL) { rle->res = rv; rle->start = rman_get_start(rv); rle->end = rman_get_end(rv); rle->count = rman_get_size(rv); } return (rv); } /** * Default bhndb(4) implementation of BUS_RELEASE_RESOURCE(). */ static int bhndb_release_resource(device_t dev, device_t child, int type, int rid, struct resource *r) { struct bhndb_softc *sc; struct resource_list_entry *rle; bool passthrough; int error; 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 (bhndb_get_rman(sc, child, type) == NULL) { return (BUS_RELEASE_RESOURCE(device_get_parent(sc->parent_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); } /** * Default bhndb(4) implementation of BUS_ADJUST_RESOURCE(). */ static int bhndb_adjust_resource(device_t dev, device_t child, int type, struct resource *r, rman_res_t start, rman_res_t end) { struct bhndb_softc *sc; struct rman *rm; rman_res_t mstart, mend; int error; sc = device_get_softc(dev); error = 0; /* Delegate to our parent device's bus if the requested resource type * isn't handled locally. */ rm = bhndb_get_rman(sc, child, type); if (rm == NULL) { return (BUS_ADJUST_RESOURCE(device_get_parent(sc->parent_dev), child, type, r, start, end)); } /* Verify basic constraints */ if (end <= start) return (EINVAL); if (!rman_is_region_manager(r, rm)) return (ENXIO); BHNDB_LOCK(sc); /* If not active, allow any range permitted by the resource manager */ if (!(rman_get_flags(r) & RF_ACTIVE)) goto done; /* 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; if (start < mstart || end > mend) { error = EINVAL; goto done; } /* Fall through */ done: if (!error) error = rman_adjust_resource(r, start, end); BHNDB_UNLOCK(sc); return (error); } /** * Initialize child resource @p r with a virtual address, tag, and handle * copied from @p parent, adjusted to contain only the range defined by * @p offsize and @p size. * * @param r The register to be initialized. * @param parent The parent bus resource that fully contains the subregion. * @param offset The subregion offset within @p parent. * @param size The subregion size. * @p r. */ static int bhndb_init_child_resource(struct resource *r, struct resource *parent, bhnd_size_t offset, bhnd_size_t size) { bus_space_handle_t bh, child_bh; bus_space_tag_t bt; uintptr_t vaddr; int error; /* Fetch the parent resource's real bus values */ vaddr = (uintptr_t) rman_get_virtual(parent); bt = rman_get_bustag(parent); bh = rman_get_bushandle(parent); /* Configure child resource with window-adjusted real bus values */ vaddr += offset; error = bus_space_subregion(bt, bh, offset, size, &child_bh); if (error) return (error); rman_set_virtual(r, (void *) vaddr); rman_set_bustag(r, bt); rman_set_bushandle(r, child_bh); return (0); } /** * Attempt activation of a fixed register window mapping for @p child. * * @param sc BHNDB device state. * @param region The static region definition capable of mapping @p r. * @param child A child requesting resource activation. * @param type Resource type. * @param rid Resource identifier. * @param r Resource to be activated. * * @retval 0 if @p r was activated successfully * @retval ENOENT if no fixed register window was found. * @retval non-zero if @p r could not be activated. */ static int bhndb_activate_static_region(struct bhndb_softc *sc, struct bhndb_region *region, device_t child, int type, int rid, struct resource *r) { struct resource *bridge_res; const struct bhndb_regwin *win; bhnd_size_t parent_offset; rman_res_t r_start, r_size; int error; win = region->static_regwin; KASSERT(win != NULL && BHNDB_REGWIN_T_IS_STATIC(win->win_type), ("can't activate non-static region")); r_start = rman_get_start(r); r_size = rman_get_size(r); /* Find the corresponding bridge resource */ bridge_res = bhndb_host_resource_for_regwin(sc->bus_res->res, win); if (bridge_res == NULL) return (ENXIO); /* Calculate subregion offset within the parent resource */ parent_offset = r_start - region->addr; parent_offset += win->win_offset; /* Configure resource with its real bus values. */ error = bhndb_init_child_resource(r, bridge_res, parent_offset, r_size); if (error) return (error); /* Mark active */ if ((error = rman_activate_resource(r))) return (error); return (0); } /** * Attempt to allocate/retain a dynamic register window for @p r, returning * the retained window. * * @param sc The bhndb driver state. * @param r The resource for which a window will be retained. */ static struct bhndb_dw_alloc * bhndb_retain_dynamic_window(struct bhndb_softc *sc, struct resource *r) { struct bhndb_dw_alloc *dwa; rman_res_t r_start, r_size; int error; BHNDB_LOCK_ASSERT(sc, MA_OWNED); r_start = rman_get_start(r); r_size = rman_get_size(r); /* Look for an existing dynamic window we can reference */ dwa = bhndb_dw_find_mapping(sc->bus_res, r_start, r_size); if (dwa != NULL) { if (bhndb_dw_retain(sc->bus_res, dwa, r) == 0) return (dwa); return (NULL); } /* Otherwise, try to reserve a free window */ dwa = bhndb_dw_next_free(sc->bus_res); if (dwa == NULL) { /* No free windows */ return (NULL); } /* Window must be large enough to map the entire resource */ if (dwa->win->win_size < rman_get_size(r)) return (NULL); /* Set the window target */ error = bhndb_dw_set_addr(sc->dev, sc->bus_res, dwa, rman_get_start(r), rman_get_size(r)); if (error) { device_printf(sc->dev, "dynamic window initialization " "for 0x%llx-0x%llx failed: %d\n", (unsigned long long) r_start, (unsigned long long) r_start + r_size - 1, error); return (NULL); } /* Add our reservation */ if (bhndb_dw_retain(sc->bus_res, dwa, r)) return (NULL); return (dwa); } /** * Activate a resource using any viable static or dynamic register window. * * @param sc The bhndb driver state. * @param child The child holding ownership of @p r. * @param type The type of the resource to be activated. * @param rid The resource ID of @p r. * @param r The resource to be activated * @param[out] indirect On error and if not NULL, will be set to 'true' if * the caller should instead use an indirect resource mapping. * * @retval 0 success * @retval non-zero activation failed. */ static int bhndb_try_activate_resource(struct bhndb_softc *sc, device_t child, int type, int rid, struct resource *r, bool *indirect) { struct bhndb_region *region; struct bhndb_dw_alloc *dwa; bhndb_priority_t dw_priority; rman_res_t r_start, r_size; rman_res_t parent_offset; int error; BHNDB_LOCK_ASSERT(sc, MA_NOTOWNED); 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); /* Activate native addrspace resources using the host address space */ if (bhndb_get_addrspace(sc, child) == BHNDB_ADDRSPACE_NATIVE) { struct resource *parent; /* Find the bridge resource referenced by the child */ parent = bhndb_host_resource_for_range(sc->bus_res->res, type, r_start, r_size); if (parent == NULL) { device_printf(sc->dev, "host resource not found " "for 0x%llx-0x%llx\n", (unsigned long long) r_start, (unsigned long long) r_start + r_size - 1); return (ENOENT); } /* Initialize child resource with the real bus values */ error = bhndb_init_child_resource(r, parent, r_start - rman_get_start(parent), r_size); if (error) return (error); /* Try to activate child resource */ return (rman_activate_resource(r)); } /* Default to low priority */ dw_priority = BHNDB_PRIORITY_LOW; /* Look for a bus region matching the resource's address range */ region = bhndb_find_resource_region(sc->bus_res, r_start, r_size); if (region != NULL) dw_priority = region->priority; /* Prefer static mappings over consuming a dynamic windows. */ if (region && region->static_regwin) { error = bhndb_activate_static_region(sc, region, child, type, rid, r); if (error) device_printf(sc->dev, "static window allocation " "for 0x%llx-0x%llx failed\n", (unsigned long long) r_start, (unsigned long long) r_start + r_size - 1); return (error); } /* A dynamic window will be required; is this resource high enough * priority to be reserved a dynamic window? */ if (dw_priority < sc->bus_res->min_prio) { if (indirect) *indirect = true; return (ENOMEM); } /* Find and retain a usable window */ BHNDB_LOCK(sc); { dwa = bhndb_retain_dynamic_window(sc, r); } BHNDB_UNLOCK(sc); if (dwa == NULL) { if (indirect) *indirect = true; return (ENOMEM); } /* Configure resource with its real bus values. */ parent_offset = dwa->win->win_offset; parent_offset += r_start - dwa->target; error = bhndb_init_child_resource(r, dwa->parent_res, parent_offset, dwa->win->win_size); if (error) goto failed; /* Mark active */ if ((error = rman_activate_resource(r))) goto failed; return (0); failed: /* Release our region allocation. */ BHNDB_LOCK(sc); bhndb_dw_release(sc->bus_res, dwa, r); BHNDB_UNLOCK(sc); return (error); } /** * Default bhndb(4) implementation of BUS_ACTIVATE_RESOURCE(). */ static int bhndb_activate_resource(device_t dev, device_t child, int type, int rid, struct resource *r) { struct bhndb_softc *sc = device_get_softc(dev); /* Delegate directly to our parent device's bus if the requested * resource type isn't handled locally. */ if (bhndb_get_rman(sc, child, type) == NULL) { return (BUS_ACTIVATE_RESOURCE(device_get_parent(sc->parent_dev), child, type, rid, r)); } return (bhndb_try_activate_resource(sc, child, type, rid, r, NULL)); } /** * Default bhndb(4) implementation of BUS_DEACTIVATE_RESOURCE(). */ static int bhndb_deactivate_resource(device_t dev, device_t child, int type, int rid, struct resource *r) { struct bhndb_dw_alloc *dwa; struct bhndb_softc *sc; struct rman *rm; int error; sc = device_get_softc(dev); /* Delegate directly to our parent device's bus if the requested * resource type isn't handled locally. */ rm = bhndb_get_rman(sc, child, type); if (rm == NULL) { return (BUS_DEACTIVATE_RESOURCE( device_get_parent(sc->parent_dev), child, type, rid, r)); } /* Mark inactive */ if ((error = rman_deactivate_resource(r))) return (error); switch (type) { case SYS_RES_IRQ: /* No bridge-level state to be freed */ return (0); case SYS_RES_MEMORY: /* Free any dynamic window allocation. */ if (bhndb_get_addrspace(sc, child) == BHNDB_ADDRSPACE_BRIDGED) { BHNDB_LOCK(sc); 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); } } /** * Default bhndb(4) implementation of BUS_GET_RESOURCE_LIST(). */ static struct resource_list * bhndb_get_resource_list(device_t dev, device_t child) { struct bhndb_devinfo *dinfo = device_get_ivars(child); return (&dinfo->resources); } /** * Default bhndb(4) implementation of BHND_BUS_ACTIVATE_RESOURCE(). * * For BHNDB_ADDRSPACE_NATIVE children, all resources are activated as direct * resources via BUS_ACTIVATE_RESOURCE(). * * 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, int type, int rid, struct bhnd_resource *r) { struct bhndb_softc *sc; struct bhndb_region *region; bhndb_priority_t r_prio; rman_res_t r_start, r_size; int error; bool indirect; KASSERT(!r->direct, ("direct flag set on inactive resource")); KASSERT(!(rman_get_flags(r->res) & RF_ACTIVE), ("RF_ACTIVE set on inactive resource")); sc = device_get_softc(dev); /* Delegate directly to BUS_ACTIVATE_RESOURCE() if the requested * resource type isn't handled locally. */ if (bhndb_get_rman(sc, child, type) == NULL) { error = BUS_ACTIVATE_RESOURCE(dev, child, type, rid, r->res); if (error == 0) r->direct = true; return (error); } r_start = rman_get_start(r->res); r_size = rman_get_size(r->res); /* 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) { 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 */ error = bhndb_try_activate_resource(sc, child, type, rid, r->res, &indirect); if (!error) { r->direct = true; } else if (indirect) { /* The request was valid, but no viable register window is * available; indirection must be employed. */ error = 0; r->direct = false; } if (BHNDB_DEBUG(PRIO) && bhndb_get_addrspace(sc, child) == BHNDB_ADDRSPACE_BRIDGED) { device_printf(child, "activated 0x%llx-0x%llx as %s " "resource\n", (unsigned long long) r_start, (unsigned long long) r_start + r_size - 1, r->direct ? "direct" : "indirect"); } return (error); } /** * Default bhndb(4) implementation of BHND_BUS_DEACTIVATE_RESOURCE(). */ static int bhndb_deactivate_bhnd_resource(device_t dev, device_t child, int type, int rid, struct bhnd_resource *r) { int error; /* Indirect resources don't require activation */ if (!r->direct) return (0); KASSERT(rman_get_flags(r->res) & RF_ACTIVE, ("RF_ACTIVE not set on direct resource")); /* Perform deactivation */ error = BUS_DEACTIVATE_RESOURCE(dev, child, type, rid, r->res); if (!error) r->direct = false; return (error); } /** * Slow path for bhndb_io_resource(). * * Iterates over the existing allocated dynamic windows looking for a viable * in-use region; the first matching region is returned. */ static struct bhndb_dw_alloc * bhndb_io_resource_slow(struct bhndb_softc *sc, bus_addr_t addr, bus_size_t size, bus_size_t *offset) { struct bhndb_resources *br; struct bhndb_dw_alloc *dwa; BHNDB_LOCK_ASSERT(sc, MA_OWNED); br = sc->bus_res; /* Search for an existing dynamic mapping of this address range. * Static regions are not searched, as a statically mapped * region would never be allocated as an indirect resource. */ for (size_t i = 0; i < br->dwa_count; i++) { const struct bhndb_regwin *win; dwa = &br->dw_alloc[i]; win = dwa->win; KASSERT(win->win_type == BHNDB_REGWIN_T_DYN, ("invalid register window type")); /* Verify the range */ if (addr < dwa->target) continue; if (addr + size > dwa->target + win->win_size) continue; /* Found */ *offset = dwa->win->win_offset; *offset += addr - dwa->target; return (dwa); } /* not found */ return (NULL); } /** * Return a borrowed reference to a bridge resource allocation record capable * of handling bus I/O requests of @p size at @p addr. * * This will either return a reference to an existing allocation * record mapping the requested space, or will configure and return a free * allocation record. * * Will panic if a usable record cannot be found. * * @param sc Bridge driver state. * @param addr The I/O target address. * @param size The size of the I/O operation to be performed at @p addr. * @param[out] offset The offset within the returned resource at which * to perform the I/O request. */ static inline struct bhndb_dw_alloc * bhndb_io_resource(struct bhndb_softc *sc, bus_addr_t addr, bus_size_t size, bus_size_t *offset) { struct bhndb_resources *br; struct bhndb_dw_alloc *dwa; int error; BHNDB_LOCK_ASSERT(sc, MA_OWNED); br = sc->bus_res; /* Try to fetch a free window */ dwa = bhndb_dw_next_free(br); /* * If no dynamic windows are available, look for an existing * region that maps the target range. * * If none are found, this is a child driver bug -- our window * over-commit should only fail in the case where a child driver leaks * resources, or perform operations out-of-order. * * Broadcom HND chipsets are designed to not require register window * swapping during execution; as long as the child devices are * attached/detached correctly, using the hardware's required order * of operations, there should always be a window available for the * current operation. */ if (dwa == NULL) { dwa = bhndb_io_resource_slow(sc, addr, size, offset); if (dwa == NULL) { panic("register windows exhausted attempting to map " "0x%llx-0x%llx\n", (unsigned long long) addr, (unsigned long long) addr+size-1); } return (dwa); } /* Adjust the window if the I/O request won't fit in the current * target range. */ if (addr < dwa->target || addr > dwa->target + dwa->win->win_size || (dwa->target + dwa->win->win_size) - addr < size) { error = bhndb_dw_set_addr(sc->dev, sc->bus_res, dwa, addr, size); if (error) { panic("failed to set register window target mapping " "0x%llx-0x%llx\n", (unsigned long long) addr, (unsigned long long) addr+size-1); } } /* Calculate the offset and return */ *offset = (addr - dwa->target) + dwa->win->win_offset; return (dwa); } /* * BHND_BUS_(READ|WRITE_* implementations */ /* bhndb_bus_(read|write) common implementation */ #define BHNDB_IO_COMMON_SETUP(_io_size) \ struct bhndb_softc *sc; \ struct bhndb_dw_alloc *dwa; \ struct resource *io_res; \ bus_size_t io_offset; \ \ sc = device_get_softc(dev); \ \ BHNDB_LOCK(sc); \ dwa = bhndb_io_resource(sc, rman_get_start(r->res) + \ offset, _io_size, &io_offset); \ io_res = dwa->parent_res; \ \ KASSERT(!r->direct, \ ("bhnd_bus slow path used for direct resource")); \ \ KASSERT(rman_get_flags(io_res) & RF_ACTIVE, \ ("i/o resource is not active")); #define BHNDB_IO_COMMON_TEARDOWN() \ BHNDB_UNLOCK(sc); /* Defines a bhndb_bus_read_* method implementation */ #define BHNDB_IO_READ(_type, _name) \ static _type \ bhndb_bus_read_ ## _name (device_t dev, device_t child, \ struct bhnd_resource *r, bus_size_t offset) \ { \ _type v; \ BHNDB_IO_COMMON_SETUP(sizeof(_type)); \ v = bus_read_ ## _name (io_res, io_offset); \ BHNDB_IO_COMMON_TEARDOWN(); \ \ return (v); \ } /* Defines a bhndb_bus_write_* method implementation */ #define BHNDB_IO_WRITE(_type, _name) \ static void \ bhndb_bus_write_ ## _name (device_t dev, device_t child, \ struct bhnd_resource *r, bus_size_t offset, _type value) \ { \ BHNDB_IO_COMMON_SETUP(sizeof(_type)); \ bus_write_ ## _name (io_res, io_offset, value); \ BHNDB_IO_COMMON_TEARDOWN(); \ } /* Defines a bhndb_bus_(read|write|set)_(multi|region)_* method */ #define BHNDB_IO_MISC(_type, _ptr, _op, _size) \ static void \ bhndb_bus_ ## _op ## _ ## _size (device_t dev, \ device_t child, struct bhnd_resource *r, bus_size_t offset, \ _type _ptr datap, bus_size_t count) \ { \ BHNDB_IO_COMMON_SETUP(sizeof(_type) * count); \ bus_ ## _op ## _ ## _size (io_res, io_offset, \ datap, count); \ BHNDB_IO_COMMON_TEARDOWN(); \ } /* Defines a complete set of read/write methods */ #define BHNDB_IO_METHODS(_type, _size) \ BHNDB_IO_READ(_type, _size) \ BHNDB_IO_WRITE(_type, _size) \ \ BHNDB_IO_READ(_type, stream_ ## _size) \ BHNDB_IO_WRITE(_type, stream_ ## _size) \ \ BHNDB_IO_MISC(_type, *, read_multi, _size) \ BHNDB_IO_MISC(_type, *, write_multi, _size) \ \ BHNDB_IO_MISC(_type, *, read_multi_stream, _size) \ BHNDB_IO_MISC(_type, *, write_multi_stream, _size) \ \ BHNDB_IO_MISC(_type, , set_multi, _size) \ BHNDB_IO_MISC(_type, , set_region, _size) \ BHNDB_IO_MISC(_type, *, read_region, _size) \ BHNDB_IO_MISC(_type, *, write_region, _size) \ \ BHNDB_IO_MISC(_type, *, read_region_stream, _size) \ BHNDB_IO_MISC(_type, *, write_region_stream, _size) BHNDB_IO_METHODS(uint8_t, 1); BHNDB_IO_METHODS(uint16_t, 2); BHNDB_IO_METHODS(uint32_t, 4); /** * Default bhndb(4) implementation of BHND_BUS_BARRIER(). */ static void bhndb_bus_barrier(device_t dev, device_t child, struct bhnd_resource *r, bus_size_t offset, bus_size_t length, int flags) { BHNDB_IO_COMMON_SETUP(length); bus_barrier(io_res, io_offset + offset, length, flags); 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 BHND_BUS_GET_DMA_TRANSLATION(). + */ +static inline int +bhndb_get_dma_translation(device_t dev, device_t child, u_int width, + uint32_t flags, bus_dma_tag_t *dmat, + struct bhnd_dma_translation *translation) +{ + struct bhndb_softc *sc; + const struct bhndb_hwcfg *hwcfg; + const struct bhnd_dma_translation *match; + bus_dma_tag_t match_dmat; + bhnd_addr_t addr_mask, match_addr_mask; + + sc = device_get_softc(dev); + hwcfg = sc->bus_res->cfg; + + /* Is DMA supported? */ + if (sc->bus_res->res->dma_tags == NULL) + return (ENODEV); + + /* Find the best matching descriptor for the requested type */ + addr_mask = BHND_DMA_ADDR_BITMASK(width); + + match = NULL; + match_addr_mask = 0x0; + match_dmat = NULL; + + for (size_t i = 0; i < sc->bus_res->res->num_dma_tags; i++) { + const struct bhnd_dma_translation *dwin; + bhnd_addr_t masked; + + dwin = &hwcfg->dma_translations[i]; + + /* The base address must be device addressable */ + if ((dwin->base_addr & addr_mask) != dwin->base_addr) + continue; + + /* The flags must match */ + if ((dwin->flags & flags) != flags) + continue; + + /* The window must cover at least part of our addressable + * range */ + masked = (dwin->addr_mask | dwin->addrext_mask) & addr_mask; + if (masked == 0) + continue; + + /* Is this a better match? */ + if (match == NULL || masked > match_addr_mask) { + match = dwin; + match_addr_mask = masked; + match_dmat = sc->bus_res->res->dma_tags[i]; + } + } + + if (match == NULL || match_addr_mask == 0) + return (ENOENT); + + if (dmat != NULL) + *dmat = match_dmat; + + if (translation != NULL) + *translation = *match; + + return (0); +} + +/** * Default bhndb(4) implementation of BUS_GET_DMA_TAG(). */ static bus_dma_tag_t bhndb_get_dma_tag(device_t dev, device_t child) { - // TODO - return (NULL); + struct bhndb_softc *sc = device_get_softc(dev); + + /* + * A bridge may have multiple DMA translation descriptors, each with + * their own incompatible restrictions; drivers should in general call + * BHND_BUS_GET_DMA_TRANSLATION() to fetch both the best available DMA + * translation, and its corresponding DMA tag. + * + * Child drivers that do not use BHND_BUS_GET_DMA_TRANSLATION() are + * responsible for creating their own restricted DMA tag; since we + * cannot do this for them in BUS_GET_DMA_TAG(), we simply return the + * bridge parent's DMA tag directly; + */ + return (bus_get_dma_tag(sc->parent_dev)); } static device_method_t bhndb_methods[] = { /* Device interface */ \ DEVMETHOD(device_probe, bhndb_generic_probe), DEVMETHOD(device_detach, bhndb_generic_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bhndb_generic_suspend), DEVMETHOD(device_resume, bhndb_generic_resume), /* Bus interface */ DEVMETHOD(bus_probe_nomatch, bhndb_probe_nomatch), DEVMETHOD(bus_print_child, bhndb_print_child), DEVMETHOD(bus_child_pnpinfo_str, bhndb_child_pnpinfo_str), DEVMETHOD(bus_child_location_str, bhndb_child_location_str), DEVMETHOD(bus_add_child, bhndb_add_child), DEVMETHOD(bus_child_deleted, bhndb_child_deleted), DEVMETHOD(bus_alloc_resource, bhndb_alloc_resource), DEVMETHOD(bus_release_resource, bhndb_release_resource), DEVMETHOD(bus_activate_resource, bhndb_activate_resource), DEVMETHOD(bus_deactivate_resource, bhndb_deactivate_resource), 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), DEVMETHOD(bus_adjust_resource, bhndb_adjust_resource), DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), DEVMETHOD(bus_delete_resource, bus_generic_rl_delete_resource), DEVMETHOD(bus_get_resource_list, bhndb_get_resource_list), DEVMETHOD(bus_read_ivar, bhndb_read_ivar), DEVMETHOD(bus_write_ivar, bhndb_write_ivar), /* BHNDB interface */ DEVMETHOD(bhndb_get_chipid, bhndb_get_chipid), DEVMETHOD(bhndb_is_core_disabled, bhndb_is_core_disabled), DEVMETHOD(bhndb_get_hostb_core, bhndb_get_hostb_core), DEVMETHOD(bhndb_suspend_resource, bhndb_suspend_resource), DEVMETHOD(bhndb_resume_resource, bhndb_resume_resource), /* BHND interface */ DEVMETHOD(bhnd_bus_get_chipid, bhndb_get_chipid), 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_dma_translation, bhndb_get_dma_translation), DEVMETHOD(bhnd_bus_get_service_registry,bhndb_get_service_registry), DEVMETHOD(bhnd_bus_register_provider, bhnd_bus_generic_sr_register_provider), DEVMETHOD(bhnd_bus_deregister_provider, bhnd_bus_generic_sr_deregister_provider), DEVMETHOD(bhnd_bus_retain_provider, bhnd_bus_generic_sr_retain_provider), DEVMETHOD(bhnd_bus_release_provider, bhnd_bus_generic_sr_release_provider), DEVMETHOD(bhnd_bus_read_1, bhndb_bus_read_1), DEVMETHOD(bhnd_bus_read_2, bhndb_bus_read_2), DEVMETHOD(bhnd_bus_read_4, bhndb_bus_read_4), DEVMETHOD(bhnd_bus_write_1, bhndb_bus_write_1), DEVMETHOD(bhnd_bus_write_2, bhndb_bus_write_2), DEVMETHOD(bhnd_bus_write_4, bhndb_bus_write_4), DEVMETHOD(bhnd_bus_read_stream_1, bhndb_bus_read_stream_1), DEVMETHOD(bhnd_bus_read_stream_2, bhndb_bus_read_stream_2), DEVMETHOD(bhnd_bus_read_stream_4, bhndb_bus_read_stream_4), DEVMETHOD(bhnd_bus_write_stream_1, bhndb_bus_write_stream_1), DEVMETHOD(bhnd_bus_write_stream_2, bhndb_bus_write_stream_2), DEVMETHOD(bhnd_bus_write_stream_4, bhndb_bus_write_stream_4), DEVMETHOD(bhnd_bus_read_multi_1, bhndb_bus_read_multi_1), DEVMETHOD(bhnd_bus_read_multi_2, bhndb_bus_read_multi_2), DEVMETHOD(bhnd_bus_read_multi_4, bhndb_bus_read_multi_4), DEVMETHOD(bhnd_bus_write_multi_1, bhndb_bus_write_multi_1), DEVMETHOD(bhnd_bus_write_multi_2, bhndb_bus_write_multi_2), DEVMETHOD(bhnd_bus_write_multi_4, bhndb_bus_write_multi_4), DEVMETHOD(bhnd_bus_read_multi_stream_1, bhndb_bus_read_multi_stream_1), DEVMETHOD(bhnd_bus_read_multi_stream_2, bhndb_bus_read_multi_stream_2), DEVMETHOD(bhnd_bus_read_multi_stream_4, bhndb_bus_read_multi_stream_4), DEVMETHOD(bhnd_bus_write_multi_stream_1,bhndb_bus_write_multi_stream_1), DEVMETHOD(bhnd_bus_write_multi_stream_2,bhndb_bus_write_multi_stream_2), DEVMETHOD(bhnd_bus_write_multi_stream_4,bhndb_bus_write_multi_stream_4), DEVMETHOD(bhnd_bus_set_multi_1, bhndb_bus_set_multi_1), DEVMETHOD(bhnd_bus_set_multi_2, bhndb_bus_set_multi_2), DEVMETHOD(bhnd_bus_set_multi_4, bhndb_bus_set_multi_4), DEVMETHOD(bhnd_bus_set_region_1, bhndb_bus_set_region_1), DEVMETHOD(bhnd_bus_set_region_2, bhndb_bus_set_region_2), DEVMETHOD(bhnd_bus_set_region_4, bhndb_bus_set_region_4), DEVMETHOD(bhnd_bus_read_region_1, bhndb_bus_read_region_1), DEVMETHOD(bhnd_bus_read_region_2, bhndb_bus_read_region_2), DEVMETHOD(bhnd_bus_read_region_4, bhndb_bus_read_region_4), DEVMETHOD(bhnd_bus_write_region_1, bhndb_bus_write_region_1), DEVMETHOD(bhnd_bus_write_region_2, bhndb_bus_write_region_2), DEVMETHOD(bhnd_bus_write_region_4, bhndb_bus_write_region_4), DEVMETHOD(bhnd_bus_read_region_stream_1,bhndb_bus_read_region_stream_1), DEVMETHOD(bhnd_bus_read_region_stream_2,bhndb_bus_read_region_stream_2), DEVMETHOD(bhnd_bus_read_region_stream_4,bhndb_bus_read_region_stream_4), DEVMETHOD(bhnd_bus_write_region_stream_1,bhndb_bus_write_region_stream_1), DEVMETHOD(bhnd_bus_write_region_stream_2,bhndb_bus_write_region_stream_2), DEVMETHOD(bhnd_bus_write_region_stream_4,bhndb_bus_write_region_stream_4), DEVMETHOD(bhnd_bus_barrier, bhndb_bus_barrier), DEVMETHOD_END }; devclass_t bhndb_devclass; DEFINE_CLASS_0(bhndb, bhndb_driver, bhndb_methods, sizeof(struct bhndb_softc)); MODULE_VERSION(bhndb, 1); MODULE_DEPEND(bhndb, bhnd, 1, 1, 1); Index: head/sys/dev/bhnd/bhndb/bhndb.h =================================================================== --- head/sys/dev/bhnd/bhndb/bhndb.h (revision 326079) +++ head/sys/dev/bhnd/bhndb/bhndb.h (revision 326080) @@ -1,174 +1,175 @@ /*- * Copyright (c) 2015 Landon Fuller * All rights reserved. * * 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 _BHND_BHNDB_H_ #define _BHND_BHNDB_H_ #include #include #include #include #include #include #include "bhndb_bus_if.h" extern devclass_t bhndb_devclass; DECLARE_CLASS(bhnd_bhndb_driver); int bhndb_attach_bridge(device_t parent, device_t *bhndb, int unit); /** * bhndb register window types. */ typedef enum { BHNDB_REGWIN_T_CORE, /**< Fixed mapping of a core port region. */ BHNDB_REGWIN_T_SPROM, /**< Fixed mapping of device SPROM */ BHNDB_REGWIN_T_DYN, /**< A dynamically configurable window */ BHNDB_REGWIN_T_INVALID /**< Invalid type */ } bhndb_regwin_type_t; /** * Evaluates to true if @p _rt defines a static mapping. * * @param _rt A bhndb_regwin_type_t value. */ #define BHNDB_REGWIN_T_IS_STATIC(_rt) \ ((_rt) == BHNDB_REGWIN_T_CORE || \ (_rt) == BHNDB_REGWIN_T_SPROM) /** * bhndb register window definition. */ struct bhndb_regwin { bhndb_regwin_type_t win_type; /**< window type */ bus_size_t win_offset; /**< offset of the window within the resource */ bus_size_t win_size; /**< size of the window */ /** Resource identification */ struct { int type; /**< resource type */ int rid; /**< resource id */ } res; union { /** Core-specific register window (BHNDB_REGWIN_T_CORE). */ struct { bhnd_devclass_t class; /**< mapped core's class */ u_int unit; /**< mapped core's unit */ bhnd_port_type port_type; /**< mapped port type */ u_int port; /**< mapped port number */ u_int region; /**< mapped region number */ } core; /** SPROM register window (BHNDB_REGWIN_T_SPROM). */ struct {} sprom; /** Dynamic register window (BHNDB_REGWIN_T_DYN). */ struct { bus_size_t cfg_offset; /**< window address config offset. */ } dyn; } d; }; #define BHNDB_REGWIN_TABLE_END { BHNDB_REGWIN_T_INVALID, 0, 0, { 0, 0 } } /** * Bridge hardware configuration. * - * Provides the bridge's register/address mappings, and the resources - * via which those mappings may be accessed. + * Provides the bridge's DMA address translation descriptions, register/address + * mappings, and the resources via which those mappings may be accessed. */ struct bhndb_hwcfg { - const struct resource_spec *resource_specs; - const struct bhndb_regwin *register_windows; + const struct resource_spec *resource_specs; /**< resources required by our register windows */ + const struct bhndb_regwin *register_windows; /**< register window table */ + const struct bhnd_dma_translation *dma_translations; /**< DMA address translation table, or NULL if DMA is not supported */ }; /** * Hardware specification entry. * * Defines a set of match criteria that may be used to determine the * register map and resource configuration for a bhndb bridge device. */ struct bhndb_hw { const char *name; /**< configuration name */ const struct bhnd_core_match *hw_reqs; /**< match requirements */ u_int num_hw_reqs; /**< number of match requirements */ const struct bhndb_hwcfg *cfg; /**< associated hardware configuration */ }; /** * bhndb resource allocation priorities. */ typedef enum { /** No direct resources should ever be allocated for this device. */ BHNDB_PRIORITY_NONE = 0, /** Allocate a direct resource if available after serving all other * higher-priority requests. */ BHNDB_PRIORITY_LOW = 1, /** Direct resource allocation is preferred, but not necessary * for reasonable runtime performance. */ BHNDB_PRIORITY_DEFAULT = 2, /** Indirect resource allocation would incur high runtime overhead. */ BHNDB_PRIORITY_HIGH = 3 } bhndb_priority_t; /** * Port resource priority descriptor. */ struct bhndb_port_priority { bhnd_port_type type; /**< port type. */ u_int port; /**< port */ u_int region; /**< region */ bhndb_priority_t priority; /**< port priority */ }; /** * Core resource priority descriptor. */ struct bhndb_hw_priority { struct bhnd_core_match match; /**< core match descriptor */ bhndb_priority_t priority; /**< core-level priority */ const struct bhndb_port_priority *ports; /**< port priorities */ u_int num_ports; /**< number of port priority records. */ }; #define BHNDB_HW_PRIORITY_TABLE_END { {}, BHNDB_PRIORITY_NONE, NULL, 0 } #endif /* _BHND_BHNDB_H_ */ Index: head/sys/dev/bhnd/bhndb/bhndb_pci.c =================================================================== --- head/sys/dev/bhnd/bhndb/bhndb_pci.c (revision 326079) +++ head/sys/dev/bhnd/bhndb/bhndb_pci.c (revision 326080) @@ -1,1481 +1,1481 @@ /*- * Copyright (c) 2015-2016 Landon Fuller * 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: * 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. */ #include __FBSDID("$FreeBSD$"); /* * PCI-specific implementation for the BHNDB bridge driver. * * Provides support for bridging from a PCI parent bus to a BHND-compatible * bus (e.g. bcma or siba) via a Broadcom PCI core configured in end-point * mode. * * This driver handles all initial generic host-level PCI interactions with a * PCI/PCIe bridge core operating in endpoint mode. Once the bridged bhnd(4) * bus has been enumerated, this driver works in tandem with a core-specific * bhnd_pci_hostb driver to manage the PCI core. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bhndb_pcireg.h" #include "bhndb_pcivar.h" #include "bhndb_private.h" struct bhndb_pci_eio; 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); static int bhndb_disable_pci_clocks(device_t dev); static int bhndb_pci_compat_setregwin(device_t dev, device_t pci_dev, const struct bhndb_regwin *, bhnd_addr_t); 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); static bus_addr_t bhndb_pci_sprom_addr(struct bhndb_pci_softc *sc); static bus_size_t bhndb_pci_sprom_size(struct bhndb_pci_softc *sc); static int bhndb_pci_eio_init(struct bhndb_pci_eio *pio, device_t dev, device_t pci_dev, struct bhndb_host_resources *hr); static int bhndb_pci_eio_map(struct bhnd_erom_io *eio, bhnd_addr_t addr, bhnd_size_t size); static uint32_t bhndb_pci_eio_read(struct bhnd_erom_io *eio, bhnd_size_t offset, u_int width); #define BHNDB_PCI_MSI_COUNT 1 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 */ device_t pci_dev; /**< parent PCI device */ struct bhndb_host_resources *hr; /**< borrowed reference to host resources */ const struct bhndb_regwin *win; /**< mapped register window, or NULL */ struct resource *res; /**< resource containing the register window, or NULL if no window mapped */ bhnd_addr_t res_target; /**< current target address (if mapped) */ bool mapped; /**< true if a valid mapping exists, false otherwise */ bhnd_addr_t addr; /**< mapped address */ 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(). * * Verifies that the parent is a PCI/PCIe device. */ static int bhndb_pci_probe(device_t dev) { struct bhnd_chipid cid; struct bhnd_core_info *cores, hostb_core; struct bhndb_pci_core *entry; bhnd_devclass_t hostb_devclass; u_int ncores; device_t parent; devclass_t parent_bus, pci; int error; cores = NULL; /* Our parent must be a PCI/PCIe device. */ pci = devclass_find("pci"); parent = device_get_parent(dev); parent_bus = device_get_devclass(device_get_parent(parent)); 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"); /* fall-through */ error = BUS_PROBE_DEFAULT; cleanup: bhndb_disable_pci_clocks(dev); if (cores != NULL) free(cores, M_BHND); return (error); } /** * Attempt to allocate MSI interrupts, returning the count in @p msi_count * on success. */ static int bhndb_pci_alloc_msi(struct bhndb_pci_softc *sc, int *msi_count) { int error, count; /* Is MSI available? */ if (pci_msi_count(sc->parent) < BHNDB_PCI_MSI_COUNT) return (ENXIO); /* Allocate expected message 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 (count < BHNDB_PCI_MSI_COUNT) return (ENXIO); *msi_count = count; return (0); } static int bhndb_pci_attach(device_t dev) { struct bhndb_pci_softc *sc; struct bhnd_chipid cid; struct bhnd_core_info *cores, hostb_core; bhnd_erom_class_t *erom_class; u_int ncores; 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_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->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))) goto cleanup; /* Identify the chip and enumerate the bridged cores */ error = bhndb_pci_read_core_table(dev, &cid, &cores, &ncores, &erom_class); if (error) goto cleanup; /* Select the appropriate register window handler */ if (cid.chip_type == BHND_CHIPTYPE_SIBA) { sc->set_regwin = bhndb_pci_compat_setregwin; } else { sc->set_regwin = bhndb_pci_fast_setregwin; } /* 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) goto cleanup; /* Fix-up power on defaults for SROM-less devices. */ bhndb_init_sromless_pci_config(sc); /* Add any additional child devices */ if ((error = bhndb_pci_add_children(sc))) goto cleanup; /* Probe and attach our children */ if ((error = bus_generic_attach(dev))) goto cleanup; free(cores, M_BHND); return (0); cleanup: device_delete_children(dev); bhndb_disable_pci_clocks(sc->dev); if (sc->isrc != NULL) bhndb_free_intr_isrc(sc->isrc); if (sc->msi_count > 0) pci_release_msi(dev); if (cores != NULL) free(cores, M_BHND); pci_disable_busmaster(sc->parent); BHNDB_PCI_LOCK_DESTROY(sc); return (error); } static int bhndb_pci_detach(device_t dev) { struct bhndb_pci_softc *sc; int error; sc = device_get_softc(dev); /* Attempt to detach our children */ if ((error = bus_generic_detach(dev))) return (error); /* Perform generic bridge detach */ if ((error = bhndb_generic_detach(dev))) return (error); /* Disable clocks (if required by this hardware) */ 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->msi_count > 0) pci_release_msi(dev); /* Disable PCI bus mastering */ pci_disable_busmaster(sc->parent); BHNDB_PCI_LOCK_DESTROY(sc); return (0); } /** * Use the generic PCI bridge hardware configuration to enumerate the bridged * bhnd(4) bus' core table. * * @note This function may be safely called prior to device attach, (e.g. * from DEVICE_PROBE). * @note This function requires exclusive ownership over allocating and * configuring host bridge resources, and should only be called prior to * completion of device attach and full configuration of the bridge. * * @param dev The bhndb_pci bridge device. * @param[out] chipid On success, the parsed chip identification. * @param[out] cores On success, the enumerated core table. The * caller is responsible for freeing this table via * bhndb_pci_free_core_table(). * @param[out] ncores On success, the number of cores found in * @p cores. * @param[out] eromcls On success, a pointer to the erom class used to * parse the device enumeration table. This * argument may be NULL if the class is not * desired. * * @retval 0 success * @retval non-zero if enumerating the bridged bhnd(4) bus fails, a regular * unix error code will be returned. */ 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) { const struct bhndb_hwcfg *cfg; struct bhndb_host_resources *hr; struct bhndb_pci_eio pio; struct bhnd_core_info *erom_cores; const struct bhnd_chipid *hint; struct bhnd_chipid cid; bhnd_erom_class_t *erom_class; bhnd_erom_t *erom; device_t parent_dev; u_int erom_ncores; int error; parent_dev = device_get_parent(dev); erom = NULL; erom_cores = NULL; /* Fetch our chipid hint (if any) and generic hardware configuration */ cfg = BHNDB_BUS_GET_GENERIC_HWCFG(parent_dev, dev); hint = BHNDB_BUS_GET_CHIPID(parent_dev, dev); /* Allocate our host resources */ - if ((error = bhndb_alloc_host_resources(parent_dev, cfg, &hr))) + if ((error = bhndb_alloc_host_resources(&hr, dev, parent_dev, cfg))) return (error); /* Initialize our erom I/O state */ if ((error = bhndb_pci_eio_init(&pio, dev, parent_dev, hr))) goto failed; /* Map the first bus core from our bridged bhnd(4) bus */ error = bhndb_pci_eio_map(&pio.eio, BHND_DEFAULT_CHIPC_ADDR, BHND_DEFAULT_CORE_SIZE); if (error) goto failed; /* Probe for a usable EROM class, and read the chip identifier */ erom_class = bhnd_erom_probe_driver_classes(device_get_devclass(dev), &pio.eio, hint, &cid); if (erom_class == NULL) { device_printf(dev, "device enumeration unsupported; no " "compatible driver found\n"); error = ENXIO; goto failed; } /* Allocate EROM parser */ if ((erom = bhnd_erom_alloc(erom_class, &cid, &pio.eio)) == NULL) { device_printf(dev, "failed to allocate device enumeration " "table parser\n"); error = ENXIO; goto failed; } /* Read the full core table */ error = bhnd_erom_get_core_table(erom, &erom_cores, &erom_ncores); if (error) { device_printf(dev, "error fetching core table: %d\n", error); goto failed; } /* Provide the results to our caller */ *cores = malloc(sizeof(erom_cores[0]) * erom_ncores, M_BHND, M_WAITOK); memcpy(*cores, erom_cores, sizeof(erom_cores[0]) * erom_ncores); *ncores = erom_ncores; *chipid = cid; if (eromcls != NULL) *eromcls = erom_class; /* Clean up */ bhnd_erom_free_core_table(erom, erom_cores); bhnd_erom_free(erom); bhndb_release_host_resources(hr); return (0); failed: if (erom_cores != NULL) bhnd_erom_free_core_table(erom, erom_cores); if (erom != NULL) bhnd_erom_free(erom); bhndb_release_host_resources(hr); return (error); } static int bhndb_pci_add_children(struct bhndb_pci_softc *sc) { bus_size_t nv_sz; int error; /** * If SPROM is mapped directly into BAR0, add child NVRAM * device. */ nv_sz = bhndb_pci_sprom_size(sc); if (nv_sz > 0) { struct bhndb_devinfo *dinfo; device_t child; if (bootverbose) { device_printf(sc->dev, "found SPROM (%ju bytes)\n", (uintmax_t)nv_sz); } /* Add sprom device, ordered early enough to be available * before the bridged bhnd(4) bus is attached. */ child = BUS_ADD_CHILD(sc->dev, BHND_PROBE_ROOT + BHND_PROBE_ORDER_EARLY, "bhnd_nvram", -1); if (child == NULL) { device_printf(sc->dev, "failed to add sprom device\n"); return (ENXIO); } /* Initialize device address space and resource covering the * BAR0 SPROM shadow. */ dinfo = device_get_ivars(child); dinfo->addrspace = BHNDB_ADDRSPACE_NATIVE; error = bus_set_resource(child, SYS_RES_MEMORY, 0, bhndb_pci_sprom_addr(sc), nv_sz); if (error) { device_printf(sc->dev, "failed to register sprom resources\n"); return (error); } } return (0); } static const struct bhndb_regwin * bhndb_pci_sprom_regwin(struct bhndb_pci_softc *sc) { struct bhndb_resources *bres; const struct bhndb_hwcfg *cfg; const struct bhndb_regwin *sprom_win; bres = sc->bhndb.bus_res; cfg = bres->cfg; sprom_win = bhndb_regwin_find_type(cfg->register_windows, BHNDB_REGWIN_T_SPROM, BHNDB_PCI_V0_BAR0_SPROM_SIZE); return (sprom_win); } static bus_addr_t bhndb_pci_sprom_addr(struct bhndb_pci_softc *sc) { const struct bhndb_regwin *sprom_win; struct resource *r; /* Fetch the SPROM register window */ sprom_win = bhndb_pci_sprom_regwin(sc); KASSERT(sprom_win != NULL, ("requested sprom address on PCI_V2+")); /* Fetch the associated resource */ r = bhndb_host_resource_for_regwin(sc->bhndb.bus_res->res, sprom_win); KASSERT(r != NULL, ("missing resource for sprom window\n")); return (rman_get_start(r) + sprom_win->win_offset); } static bus_size_t bhndb_pci_sprom_size(struct bhndb_pci_softc *sc) { const struct bhndb_regwin *sprom_win; uint32_t sctl; bus_size_t sprom_sz; sprom_win = bhndb_pci_sprom_regwin(sc); /* PCI_V2 and later devices map SPROM/OTP via ChipCommon */ if (sprom_win == NULL) return (0); /* Determine SPROM size */ sctl = pci_read_config(sc->parent, BHNDB_PCI_SPROM_CONTROL, 4); if (sctl & BHNDB_PCI_SPROM_BLANK) return (0); switch (sctl & BHNDB_PCI_SPROM_SZ_MASK) { case BHNDB_PCI_SPROM_SZ_1KB: sprom_sz = (1 * 1024); break; case BHNDB_PCI_SPROM_SZ_4KB: sprom_sz = (4 * 1024); break; case BHNDB_PCI_SPROM_SZ_16KB: sprom_sz = (16 * 1024); break; case BHNDB_PCI_SPROM_SZ_RESERVED: default: device_printf(sc->dev, "invalid PCI sprom size 0x%x\n", sctl); return (0); } if (sprom_sz > sprom_win->win_size) { device_printf(sc->dev, "PCI sprom size (0x%x) overruns defined register window\n", sctl); return (0); } 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 * mapped to the wrong core. * * This function updates the SROM shadow to point the BAR0 windows at the * current PCI core. * * Applies to all PCI/PCIe revisions. */ static void bhndb_init_sromless_pci_config(struct bhndb_pci_softc *sc) { const struct bhndb_pci_core *pci_core; bus_size_t srsh_offset; u_int pci_cidx, sprom_cidx; uint16_t val; if ((sc->pci_quirks & BHNDB_PCI_QUIRK_SRSH_WAR) == 0) 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")); srsh_offset = pci_core->srsh_offset; /* Fetch the SPROM's configured core index */ 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 = 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); bhndb_pci_write_core(sc, srsh_offset, val, sizeof(val)); } } static int bhndb_pci_resume(device_t dev) { struct bhndb_pci_softc *sc; int error; sc = device_get_softc(dev); /* Enable clocks (if supported by this hardware) */ if ((error = bhndb_enable_pci_clocks(sc->dev))) return (error); /* Perform resume */ return (bhndb_generic_resume(dev)); } static int bhndb_pci_suspend(device_t dev) { struct bhndb_pci_softc *sc; int error; sc = device_get_softc(dev); /* Disable clocks (if supported by this hardware) */ if ((error = bhndb_disable_pci_clocks(sc->dev))) return (error); /* Perform suspend */ return (bhndb_generic_suspend(dev)); } static int bhndb_pci_set_window_addr(device_t dev, const struct bhndb_regwin *rw, bhnd_addr_t addr) { struct bhndb_pci_softc *sc = device_get_softc(dev); return (sc->set_regwin(sc->dev, sc->parent, rw, addr)); } /** * A siba(4) and bcma(4)-compatible bhndb_set_window_addr implementation. * * On siba(4) devices, it's possible that writing a PCI window register may * not succeed; it's necessary to immediately read the configuration register * and retry if not set to the desired value. * * This is not necessary on bcma(4) devices, but other than the overhead of * validating the register, there's no harm in performing the verification. */ static int bhndb_pci_compat_setregwin(device_t dev, device_t pci_dev, const struct bhndb_regwin *rw, bhnd_addr_t addr) { int error; int reg; if (rw->win_type != BHNDB_REGWIN_T_DYN) return (ENODEV); reg = rw->d.dyn.cfg_offset; for (u_int i = 0; i < BHNDB_PCI_BARCTRL_WRITE_RETRY; i++) { if ((error = bhndb_pci_fast_setregwin(dev, pci_dev, rw, addr))) return (error); if (pci_read_config(pci_dev, reg, 4) == addr) return (0); DELAY(10); } /* Unable to set window */ return (ENODEV); } /** * A bcma(4)-only bhndb_set_window_addr implementation. */ static int bhndb_pci_fast_setregwin(device_t dev, device_t pci_dev, const struct bhndb_regwin *rw, bhnd_addr_t addr) { /* The PCI bridge core only supports 32-bit addressing, regardless * of the bus' support for 64-bit addressing */ if (addr > UINT32_MAX) return (ERANGE); switch (rw->win_type) { case BHNDB_REGWIN_T_DYN: /* Addresses must be page aligned */ if (addr % rw->win_size != 0) return (EINVAL); pci_write_config(pci_dev, rw->d.dyn.cfg_offset, addr, 4); break; default: return (ENODEV); } return (0); } static int bhndb_pci_populate_board_info(device_t dev, device_t child, struct bhnd_board_info *info) { struct bhndb_pci_softc *sc; sc = device_get_softc(dev); /* * On a subset of Apple BCM4360 modules, always prefer the * PCI subdevice to the SPROM-supplied boardtype. * * TODO: * * Broadcom's own drivers implement this override, and then later use * the remapped BCM4360 board type to determine the required * board-specific workarounds. * * Without access to this hardware, it's unclear why this mapping * is done, and we must do the same. If we can survey the hardware * in question, it may be possible to replace this behavior with * explicit references to the SPROM-supplied boardtype(s) in our * quirk definitions. */ if (pci_get_subvendor(sc->parent) == PCI_VENDOR_APPLE) { switch (info->board_type) { case BHND_BOARD_BCM94360X29C: case BHND_BOARD_BCM94360X29CP2: case BHND_BOARD_BCM94360X51: case BHND_BOARD_BCM94360X51P2: info->board_type = 0; /* allow override below */ break; default: break; } } /* If NVRAM did not supply vendor/type info, provide the PCI * subvendor/subdevice values. */ if (info->board_vendor == 0) info->board_vendor = pci_get_subvendor(sc->parent); if (info->board_type == 0) info->board_type = pci_get_subdevice(sc->parent); return (0); } /** * 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 */ static bool bhndb_is_pcie_attached(device_t dev) { int reg; if (pci_find_cap(device_get_parent(dev), PCIY_EXPRESS, ®) == 0) return (true); return (false); } /** * Enable externally managed clocks, if required. * * Some PCI chipsets (BCM4306, possibly others) chips do not support * the idle low-power clock. Clocking must be bootstrapped at * attach/resume by directly adjusting GPIO registers exposed in the * PCI config space, and correspondingly, explicitly shutdown at * detach/suspend. * * @note This function may be safely called prior to device attach, (e.g. * from DEVICE_PROBE). * * @param dev The bhndb bridge device */ static int bhndb_enable_pci_clocks(device_t dev) { device_t pci_dev; uint32_t gpio_in, gpio_out, gpio_en; uint32_t gpio_flags; uint16_t pci_status; pci_dev = device_get_parent(dev); /* Only supported and required on PCI devices */ if (!bhndb_is_pcie_attached(dev)) return (0); /* Read state of XTAL pin */ gpio_in = pci_read_config(pci_dev, BHNDB_PCI_GPIO_IN, 4); if (gpio_in & BHNDB_PCI_GPIO_XTAL_ON) return (0); /* already enabled */ /* Fetch current config */ gpio_out = pci_read_config(pci_dev, BHNDB_PCI_GPIO_OUT, 4); gpio_en = pci_read_config(pci_dev, BHNDB_PCI_GPIO_OUTEN, 4); /* Set PLL_OFF/XTAL_ON pins to HIGH and enable both pins */ gpio_flags = (BHNDB_PCI_GPIO_PLL_OFF|BHNDB_PCI_GPIO_XTAL_ON); gpio_out |= gpio_flags; gpio_en |= gpio_flags; pci_write_config(pci_dev, BHNDB_PCI_GPIO_OUT, gpio_out, 4); pci_write_config(pci_dev, BHNDB_PCI_GPIO_OUTEN, gpio_en, 4); DELAY(1000); /* Reset PLL_OFF */ gpio_out &= ~BHNDB_PCI_GPIO_PLL_OFF; pci_write_config(pci_dev, BHNDB_PCI_GPIO_OUT, gpio_out, 4); DELAY(5000); /* Clear any PCI 'sent target-abort' flag. */ pci_status = pci_read_config(pci_dev, PCIR_STATUS, 2); pci_status &= ~PCIM_STATUS_STABORT; pci_write_config(pci_dev, PCIR_STATUS, pci_status, 2); return (0); } /** * Disable externally managed clocks, if required. * * This function may be safely called prior to device attach, (e.g. * from DEVICE_PROBE). * * @param dev The bhndb bridge device */ static int bhndb_disable_pci_clocks(device_t dev) { device_t pci_dev; uint32_t gpio_out, gpio_en; pci_dev = device_get_parent(dev); /* Only supported and required on PCI devices */ if (bhndb_is_pcie_attached(dev)) return (0); /* Fetch current config */ gpio_out = pci_read_config(pci_dev, BHNDB_PCI_GPIO_OUT, 4); gpio_en = pci_read_config(pci_dev, BHNDB_PCI_GPIO_OUTEN, 4); /* Set PLL_OFF to HIGH, XTAL_ON to LOW. */ gpio_out &= ~BHNDB_PCI_GPIO_XTAL_ON; gpio_out |= BHNDB_PCI_GPIO_PLL_OFF; pci_write_config(pci_dev, BHNDB_PCI_GPIO_OUT, gpio_out, 4); /* Enable both output pins */ gpio_en |= (BHNDB_PCI_GPIO_PLL_OFF|BHNDB_PCI_GPIO_XTAL_ON); pci_write_config(pci_dev, BHNDB_PCI_GPIO_OUTEN, gpio_en, 4); return (0); } static bhnd_clksrc bhndb_pci_pwrctl_get_clksrc(device_t dev, device_t child, bhnd_clock clock) { struct bhndb_pci_softc *sc; uint32_t gpio_out; sc = device_get_softc(dev); /* Only supported on PCI devices */ if (bhndb_is_pcie_attached(sc->dev)) return (ENODEV); /* Only ILP is supported */ if (clock != BHND_CLOCK_ILP) return (ENXIO); gpio_out = pci_read_config(sc->parent, BHNDB_PCI_GPIO_OUT, 4); if (gpio_out & BHNDB_PCI_GPIO_SCS) return (BHND_CLKSRC_PCI); else return (BHND_CLKSRC_XTAL); } static int bhndb_pci_pwrctl_gate_clock(device_t dev, device_t child, bhnd_clock clock) { struct bhndb_pci_softc *sc = device_get_softc(dev); /* Only supported on PCI devices */ if (bhndb_is_pcie_attached(sc->dev)) return (ENODEV); /* Only HT is supported */ if (clock != BHND_CLOCK_HT) return (ENXIO); return (bhndb_disable_pci_clocks(sc->dev)); } static int bhndb_pci_pwrctl_ungate_clock(device_t dev, device_t child, bhnd_clock clock) { struct bhndb_pci_softc *sc = device_get_softc(dev); /* Only supported on PCI devices */ if (bhndb_is_pcie_attached(sc->dev)) return (ENODEV); /* Only HT is supported */ if (clock != BHND_CLOCK_HT) return (ENXIO); 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_route_interrupts(device_t dev, device_t child) { struct bhndb_pci_softc *sc; struct bhnd_core_info core; uint32_t core_bit; uint32_t intmask; sc = device_get_softc(dev); if (sc->pci_quirks & BHNDB_PCI_QUIRK_SIBA_INTVEC) return (bhndb_pci_route_siba_interrupts(sc, child)); 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<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); } /** * Initialize a new bhndb PCI bridge EROM I/O instance. This EROM I/O * implementation supports mapping of the device enumeration table via the * @p hr host resources. * * @param pio The instance to be initialized. * @param dev The bridge device. * @param pci_dev The bridge's parent PCI device. * @param hr The host resources to be used to map the device * enumeration table. */ static int bhndb_pci_eio_init(struct bhndb_pci_eio *pio, device_t dev, device_t pci_dev, struct bhndb_host_resources *hr) { memset(&pio->eio, sizeof(pio->eio), 0); pio->eio.map = bhndb_pci_eio_map; pio->eio.read = bhndb_pci_eio_read; pio->eio.fini = NULL; pio->dev = dev; pio->pci_dev = pci_dev; pio->hr = hr; pio->win = NULL; pio->res = NULL; return (0); } /** * Attempt to adjust the dynamic register window backing @p pio to permit * reading @p size bytes at @p addr. * * If @p addr or @p size fall outside the existing mapped range, or if * @p pio is not backed by a dynamic register window, ENXIO will be returned. * * @param pio The bhndb PCI erom I/O state to be modified. * @param addr The address to be include */ static int bhndb_pci_eio_adjust_mapping(struct bhndb_pci_eio *pio, bhnd_addr_t addr, bhnd_size_t size) { bhnd_addr_t target; bhnd_size_t offset; int error; KASSERT(pio->win != NULL, ("missing register window")); KASSERT(pio->res != NULL, ("missing regwin resource")); KASSERT(pio->win->win_type == BHNDB_REGWIN_T_DYN, ("unexpected window type %d", pio->win->win_type)); /* The requested subrange must fall within the total mapped range */ if (addr < pio->addr || (addr - pio->addr) > pio->size || size > pio->size || (addr - pio->addr) - pio->size < size) { return (ENXIO); } /* Do we already have a useable mapping? */ if (addr >= pio->res_target && addr <= pio->res_target + pio->win->win_size && (pio->res_target + pio->win->win_size) - addr >= size) { return (0); } /* Page-align the target address */ offset = addr % pio->win->win_size; target = addr - offset; /* Configure the register window */ error = bhndb_pci_compat_setregwin(pio->dev, pio->pci_dev, pio->win, target); if (error) { device_printf(pio->dev, "failed to configure dynamic register " "window: %d\n", error); return (error); } pio->res_target = target; return (0); } /* bhnd_erom_io_map() implementation */ static int bhndb_pci_eio_map(struct bhnd_erom_io *eio, bhnd_addr_t addr, bhnd_size_t size) { struct bhndb_pci_eio *pio; const struct bhndb_regwin *regwin; struct resource *r; bhnd_addr_t target; bhnd_size_t offset; int error; pio = (struct bhndb_pci_eio *)eio; /* Locate a useable dynamic register window */ regwin = bhndb_regwin_find_type(pio->hr->cfg->register_windows, BHNDB_REGWIN_T_DYN, MIN(size, BHND_DEFAULT_CORE_SIZE)); if (regwin == NULL) { device_printf(pio->dev, "unable to map %#jx+%#jx; no " "usable dynamic register window found\n", addr, size); return (ENXIO); } /* Locate the host resource mapping our register window */ if ((r = bhndb_host_resource_for_regwin(pio->hr, regwin)) == NULL) { device_printf(pio->dev, "unable to map %#jx+%#jx; no " "usable register resource found\n", addr, size); return (ENXIO); } /* Page-align the target address */ offset = addr % regwin->win_size; target = addr - offset; /* Configure the register window */ error = bhndb_pci_compat_setregwin(pio->dev, pio->pci_dev, regwin, target); if (error) { device_printf(pio->dev, "failed to configure dynamic register " "window: %d\n", error); return (error); } /* Update our mapping state */ pio->win = regwin; pio->res = r; pio->addr = addr; pio->size = size; pio->res_target = target; return (0); } /* bhnd_erom_io_read() implementation */ static uint32_t bhndb_pci_eio_read(struct bhnd_erom_io *eio, bhnd_size_t offset, u_int width) { struct bhndb_pci_eio *pio; bhnd_addr_t addr; bus_size_t res_offset; int error; pio = (struct bhndb_pci_eio *)eio; /* Calculate absolute address */ if (BHND_SIZE_MAX - offset < pio->addr) { device_printf(pio->dev, "invalid offset %#jx+%#jx\n", pio->addr, offset); return (UINT32_MAX); } addr = pio->addr + offset; /* Adjust the mapping for our read */ if ((error = bhndb_pci_eio_adjust_mapping(pio, addr, width))) { device_printf(pio->dev, "failed to adjust register mapping: " "%d\n", error); return (UINT32_MAX); } KASSERT(pio->res_target <= addr, ("invalid mapping (%#jx vs. %#jx)", pio->res_target, addr)); /* Determine the actual read offset within our register window * resource */ res_offset = (addr - pio->res_target) + pio->win->win_offset; /* Perform our read */ switch (width) { case 1: return (bus_read_1(pio->res, res_offset)); case 2: return (bus_read_2(pio->res, res_offset)); case 4: return (bus_read_4(pio->res, res_offset)); default: panic("unsupported width: %u", width); } } static device_method_t bhndb_pci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, bhndb_pci_probe), DEVMETHOD(device_attach, bhndb_pci_attach), DEVMETHOD(device_resume, bhndb_pci_resume), DEVMETHOD(device_suspend, bhndb_pci_suspend), DEVMETHOD(device_detach, bhndb_pci_detach), /* BHND interface */ 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), /* 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 }; DEFINE_CLASS_1(bhndb, bhndb_pci_driver, bhndb_pci_methods, sizeof(struct bhndb_pci_softc), bhndb_driver); MODULE_VERSION(bhndb_pci, 1); MODULE_DEPEND(bhndb_pci, bhnd_pci_hostb, 1, 1, 1); MODULE_DEPEND(bhndb_pci, pci, 1, 1, 1); MODULE_DEPEND(bhndb_pci, bhndb, 1, 1, 1); MODULE_DEPEND(bhndb_pci, bhnd, 1, 1, 1); Index: head/sys/dev/bhnd/bhndb/bhndb_pci_hwdata.c =================================================================== --- head/sys/dev/bhnd/bhndb/bhndb_pci_hwdata.c (revision 326079) +++ head/sys/dev/bhnd/bhndb/bhndb_pci_hwdata.c (revision 326080) @@ -1,592 +1,656 @@ /*- * Copyright (c) 2015 Landon Fuller * All rights reserved. * * 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. */ #include __FBSDID("$FreeBSD$"); /* * Resource specifications and register maps for Broadcom PCI/PCIe cores * configured as PCI-BHND bridges. */ #include #include #include #include #include #include #include +#include +#include + #include "bhndbvar.h" #include "bhndb_pcireg.h" static const struct bhndb_hwcfg bhndb_pci_hwcfg_v0; static const struct bhndb_hwcfg bhndb_pci_hwcfg_v1_pci; static const struct bhndb_hwcfg bhndb_pci_hwcfg_v1_pcie; static const struct bhndb_hwcfg bhndb_pci_hwcfg_v2; static const struct bhndb_hwcfg bhndb_pci_hwcfg_v3; /** * Define a bhndb_hw match entry. * * @param _name The entry name. * @param _vers The configuration version associated with this entry. */ #define BHNDB_HW_MATCH(_name, _vers, ...) { \ .name = _name, \ .hw_reqs = _BHNDB_HW_REQ_ARRAY(__VA_ARGS__), \ .num_hw_reqs = (sizeof(_BHNDB_HW_REQ_ARRAY(__VA_ARGS__)) / \ sizeof(_BHNDB_HW_REQ_ARRAY(__VA_ARGS__)[0])), \ .cfg = &bhndb_pci_hwcfg_ ## _vers \ } #define _BHNDB_HW_REQ_ARRAY(...) (struct bhnd_core_match[]) { __VA_ARGS__ } /** * Generic PCI-SIBA bridge configuration usable with all known siba(4)-based * PCI devices; this configuration is adequate for enumerating a bridged * siba(4) bus to determine the full hardware configuration. * * @par Compatibility * - Compatible with PCI_V0, PCI_V1, PCI_V2, and PCI_V3 devices. * - Compatible with siba(4) bus enumeration. * - Compatible with bcma(4) bus enumeration if the ChipCommon core is mapped * at the default enumeration address (0x18000000). */ const struct bhndb_hwcfg bhndb_pci_siba_generic_hwcfg = { .resource_specs = (const struct resource_spec[]) { { SYS_RES_MEMORY, PCIR_BAR(0), RF_ACTIVE }, { -1, 0, 0 } }, .register_windows = (const struct bhndb_regwin[]) { /* bar0+0x0000: configurable backplane window */ { .win_type = BHNDB_REGWIN_T_DYN, .win_offset = BHNDB_PCI_V1_BAR0_WIN0_OFFSET, .win_size = BHNDB_PCI_V1_BAR0_WIN0_SIZE, .d.dyn = { .cfg_offset = BHNDB_PCI_V1_BAR0_WIN0_CONTROL }, .res = { SYS_RES_MEMORY, PCIR_BAR(0) } }, BHNDB_REGWIN_TABLE_END }, + + /* DMA unsupported under generic configuration */ + .dma_translations = NULL, }; /** * Generic PCI-BCMA bridge configuration usable with all known bcma(4)-based * PCI devices; this configuration is adequate for enumerating a bridged * bcma(4) bus to determine the full hardware configuration. * * @par Compatibility * - Compatible with PCI_V1, PCI_V2, and PCI_V3 devices. * - Compatible with both siba(4) and bcma(4) bus enumeration. */ const struct bhndb_hwcfg bhndb_pci_bcma_generic_hwcfg = { .resource_specs = (const struct resource_spec[]) { { SYS_RES_MEMORY, PCIR_BAR(0), RF_ACTIVE }, { -1, 0, 0 } }, .register_windows = (const struct bhndb_regwin[]) { /* bar0+0x0000: configurable backplane window */ { .win_type = BHNDB_REGWIN_T_DYN, .win_offset = BHNDB_PCI_V1_BAR0_WIN0_OFFSET, .win_size = BHNDB_PCI_V1_BAR0_WIN0_SIZE, .d.dyn = { .cfg_offset = BHNDB_PCI_V1_BAR0_WIN0_CONTROL, }, .res = { SYS_RES_MEMORY, PCIR_BAR(0) } }, /* bar0+0x3000: chipc core registers */ { .win_type = BHNDB_REGWIN_T_CORE, .win_offset = BHNDB_PCI_V1_BAR0_CCREGS_OFFSET, .win_size = BHNDB_PCI_V1_BAR0_CCREGS_SIZE, .d.core = { .class = BHND_DEVCLASS_CC, .unit = 0, .port = 0, .region = 0, .port_type = BHND_PORT_DEVICE }, .res = { SYS_RES_MEMORY, PCIR_BAR(0) } }, BHNDB_REGWIN_TABLE_END }, + + /* DMA unsupported under generic configuration */ + .dma_translations = NULL, }; /** * Hardware configuration tables for Broadcom HND PCI NICs. */ const struct bhndb_hw bhndb_pci_generic_hw_table[] = { /* PCI/V0 WLAN */ BHNDB_HW_MATCH("PCI/v0 WLAN", v0, /* PCI Core */ { BHND_MATCH_CORE_VENDOR (BHND_MFGID_BCM), BHND_MATCH_CORE_ID (BHND_COREID_PCI), BHND_MATCH_CORE_REV( HWREV_LTE (BHNDB_PCI_V0_MAX_PCI_HWREV)), BHND_MATCH_CORE_CLASS (BHND_DEVCLASS_PCI), BHND_MATCH_CORE_UNIT (0) }, /* 802.11 Core */ { BHND_MATCH_CORE_VENDOR (BHND_MFGID_BCM), BHND_MATCH_CORE_CLASS (BHND_DEVCLASS_WLAN), BHND_MATCH_CORE_UNIT (0) } ), /* PCI/V1 WLAN */ BHNDB_HW_MATCH("PCI/v1 WLAN", v1_pci, /* PCI Core */ { BHND_MATCH_CORE_VENDOR (BHND_MFGID_BCM), BHND_MATCH_CORE_ID (BHND_COREID_PCI), BHND_MATCH_CORE_REV( HWREV_GTE (BHNDB_PCI_V1_MIN_PCI_HWREV)), BHND_MATCH_CORE_CLASS (BHND_DEVCLASS_PCI), BHND_MATCH_CORE_UNIT (0) }, /* 802.11 Core */ { BHND_MATCH_CORE_VENDOR (BHND_MFGID_BCM), BHND_MATCH_CORE_CLASS (BHND_DEVCLASS_WLAN), BHND_MATCH_CORE_UNIT (0) } ), /* PCIE/V1 WLAN */ BHNDB_HW_MATCH("PCIe/v1 WLAN", v1_pcie, /* PCIe Core */ { BHND_MATCH_CORE_VENDOR (BHND_MFGID_BCM), BHND_MATCH_CORE_ID (BHND_COREID_PCIE), BHND_MATCH_CORE_CLASS (BHND_DEVCLASS_PCIE), BHND_MATCH_CORE_UNIT (0) }, /* ChipCommon (revision <= 31) */ { BHND_MATCH_CORE_VENDOR (BHND_MFGID_BCM), BHND_MATCH_CORE_ID (BHND_COREID_CC), BHND_MATCH_CORE_REV( HWREV_LTE (BHNDB_PCI_V1_MAX_CHIPC_HWREV)), BHND_MATCH_CORE_CLASS (BHND_DEVCLASS_CC), BHND_MATCH_CORE_UNIT (0) }, /* 802.11 Core */ { BHND_MATCH_CORE_VENDOR (BHND_MFGID_BCM), BHND_MATCH_CORE_CLASS (BHND_DEVCLASS_WLAN), BHND_MATCH_CORE_UNIT (0) } ), /* PCIE/V2 WLAN */ BHNDB_HW_MATCH("PCIe/v2 WLAN", v2, /* PCIe Core */ { BHND_MATCH_CORE_VENDOR (BHND_MFGID_BCM), BHND_MATCH_CORE_ID (BHND_COREID_PCIE), BHND_MATCH_CORE_CLASS (BHND_DEVCLASS_PCIE), BHND_MATCH_CORE_UNIT (0) }, /* ChipCommon (revision >= 32) */ { BHND_MATCH_CORE_VENDOR (BHND_MFGID_BCM), BHND_MATCH_CORE_ID (BHND_COREID_CC), BHND_MATCH_CORE_REV( HWREV_GTE (BHNDB_PCI_V2_MIN_CHIPC_HWREV)), BHND_MATCH_CORE_CLASS (BHND_DEVCLASS_CC), BHND_MATCH_CORE_UNIT (0) }, /* 802.11 Core */ { BHND_MATCH_CORE_VENDOR (BHND_MFGID_BCM), BHND_MATCH_CORE_CLASS (BHND_DEVCLASS_WLAN), BHND_MATCH_CORE_UNIT (0) } ), /* PCIE/V3 WLAN */ BHNDB_HW_MATCH("PCIe-Gen2/v3 WLAN", v3, /* PCIe Gen2 Core */ { BHND_MATCH_CORE_VENDOR (BHND_MFGID_BCM), BHND_MATCH_CORE_ID (BHND_COREID_PCIE2), BHND_MATCH_CORE_CLASS (BHND_DEVCLASS_PCIE), BHND_MATCH_CORE_UNIT (0) }, /* 802.11 Core */ { BHND_MATCH_CORE_VENDOR (BHND_MFGID_BCM), BHND_MATCH_CORE_CLASS (BHND_DEVCLASS_WLAN), BHND_MATCH_CORE_UNIT (0) } ), { NULL, NULL, 0, NULL } }; /** * PCI_V0 hardware configuration. * * Applies to: * - PCI (cid=0x804, revision <= 12) */ static const struct bhndb_hwcfg bhndb_pci_hwcfg_v0 = { .resource_specs = (const struct resource_spec[]) { { SYS_RES_MEMORY, PCIR_BAR(0), RF_ACTIVE }, { -1, 0, 0 } }, .register_windows = (const struct bhndb_regwin[]) { /* bar0+0x0000: configurable backplane window */ { .win_type = BHNDB_REGWIN_T_DYN, .win_offset = BHNDB_PCI_V0_BAR0_WIN0_OFFSET, .win_size = BHNDB_PCI_V0_BAR0_WIN0_SIZE, .d.dyn = { .cfg_offset = BHNDB_PCI_V0_BAR0_WIN0_CONTROL }, .res = { SYS_RES_MEMORY, PCIR_BAR(0) } }, /* bar0+0x1000: sprom shadow */ { .win_type = BHNDB_REGWIN_T_SPROM, .win_offset = BHNDB_PCI_V0_BAR0_SPROM_OFFSET, .win_size = BHNDB_PCI_V0_BAR0_SPROM_SIZE, .res = { SYS_RES_MEMORY, PCIR_BAR(0) } }, /* bar0+0x1800: pci core registers */ { .win_type = BHNDB_REGWIN_T_CORE, .win_offset = BHNDB_PCI_V0_BAR0_PCIREG_OFFSET, .win_size = BHNDB_PCI_V0_BAR0_PCIREG_SIZE, .d.core = { .class = BHND_DEVCLASS_PCI, .unit = 0, .port = 0, .region = 0, .port_type = BHND_PORT_DEVICE }, .res = { SYS_RES_MEMORY, PCIR_BAR(0) } }, BHNDB_REGWIN_TABLE_END }, + + .dma_translations = (const struct bhnd_dma_translation[]) { + { + .base_addr = BHND_PCI_DMA32_TRANSLATION, + .addr_mask = ~BHND_PCI_DMA32_MASK, + .addrext_mask = BHND_PCI_DMA32_MASK + }, + BHND_DMA_TRANSLATION_TABLE_END + } }; /** * PCI_V1 (PCI-only) hardware configuration (PCI version) * * Applies to: * - PCI (cid=0x804, revision >= 13) */ static const struct bhndb_hwcfg bhndb_pci_hwcfg_v1_pci = { .resource_specs = (const struct resource_spec[]) { { SYS_RES_MEMORY, PCIR_BAR(0), RF_ACTIVE }, { -1, 0, 0 } }, .register_windows = (const struct bhndb_regwin[]) { /* bar0+0x0000: configurable backplane window */ { .win_type = BHNDB_REGWIN_T_DYN, .win_offset = BHNDB_PCI_V1_BAR0_WIN0_OFFSET, .win_size = BHNDB_PCI_V1_BAR0_WIN0_SIZE, .d.dyn = { .cfg_offset = BHNDB_PCI_V1_BAR0_WIN0_CONTROL }, .res = { SYS_RES_MEMORY, PCIR_BAR(0) } }, /* bar0+0x1000: sprom shadow */ { .win_type = BHNDB_REGWIN_T_SPROM, .win_offset = BHNDB_PCI_V1_BAR0_SPROM_OFFSET, .win_size = BHNDB_PCI_V1_BAR0_SPROM_SIZE, .res = { SYS_RES_MEMORY, PCIR_BAR(0) } }, /* bar0+0x2000: pci core registers */ { .win_type = BHNDB_REGWIN_T_CORE, .win_offset = BHNDB_PCI_V1_BAR0_PCIREG_OFFSET, .win_size = BHNDB_PCI_V1_BAR0_PCIREG_SIZE, .d.core = { .class = BHND_DEVCLASS_PCI, .unit = 0, .port = 0, .region = 0, .port_type = BHND_PORT_DEVICE }, .res = { SYS_RES_MEMORY, PCIR_BAR(0) } }, /* bar0+0x3000: chipc core registers */ { .win_type = BHNDB_REGWIN_T_CORE, .win_offset = BHNDB_PCI_V1_BAR0_CCREGS_OFFSET, .win_size = BHNDB_PCI_V1_BAR0_CCREGS_SIZE, .d.core = { .class = BHND_DEVCLASS_CC, .unit = 0, .port = 0, .region = 0, .port_type = BHND_PORT_DEVICE }, .res = { SYS_RES_MEMORY, PCIR_BAR(0) } }, BHNDB_REGWIN_TABLE_END }, + + .dma_translations = (const struct bhnd_dma_translation[]) { + { + .base_addr = BHND_PCI_DMA32_TRANSLATION, + .addr_mask = ~BHND_PCI_DMA32_MASK, + .addrext_mask = BHND_PCI_DMA32_MASK + }, + BHND_DMA_TRANSLATION_TABLE_END + } }; /** * PCI_V1 hardware configuration (PCIE version). * * Applies to: * - PCIE (cid=0x820) with ChipCommon (revision <= 31) */ static const struct bhndb_hwcfg bhndb_pci_hwcfg_v1_pcie = { .resource_specs = (const struct resource_spec[]) { { SYS_RES_MEMORY, PCIR_BAR(0), RF_ACTIVE }, { -1, 0, 0 } }, .register_windows = (const struct bhndb_regwin[]) { /* bar0+0x0000: configurable backplane window */ { .win_type = BHNDB_REGWIN_T_DYN, .win_offset = BHNDB_PCI_V1_BAR0_WIN0_OFFSET, .win_size = BHNDB_PCI_V1_BAR0_WIN0_SIZE, .d.dyn = { .cfg_offset = BHNDB_PCI_V1_BAR0_WIN0_CONTROL }, .res = { SYS_RES_MEMORY, PCIR_BAR(0) } }, /* bar0+0x1000: sprom shadow */ { .win_type = BHNDB_REGWIN_T_SPROM, .win_offset = BHNDB_PCI_V1_BAR0_SPROM_OFFSET, .win_size = BHNDB_PCI_V1_BAR0_SPROM_SIZE, .res = { SYS_RES_MEMORY, PCIR_BAR(0) } }, /* bar0+0x2000: pci core registers */ { .win_type = BHNDB_REGWIN_T_CORE, .win_offset = BHNDB_PCI_V1_BAR0_PCIREG_OFFSET, .win_size = BHNDB_PCI_V1_BAR0_PCIREG_SIZE, .d.core = { .class = BHND_DEVCLASS_PCIE, .unit = 0, .port = 0, .region = 0, .port_type = BHND_PORT_DEVICE }, .res = { SYS_RES_MEMORY, PCIR_BAR(0) } }, /* bar0+0x3000: chipc core registers */ { .win_type = BHNDB_REGWIN_T_CORE, .win_offset = BHNDB_PCI_V1_BAR0_CCREGS_OFFSET, .win_size = BHNDB_PCI_V1_BAR0_CCREGS_SIZE, .d.core = { .class = BHND_DEVCLASS_CC, .unit = 0, .port = 0, .region = 0, .port_type = BHND_PORT_DEVICE }, .res = { SYS_RES_MEMORY, PCIR_BAR(0) } }, BHNDB_REGWIN_TABLE_END }, + + .dma_translations = (const struct bhnd_dma_translation[]) { + { + .base_addr = BHND_PCIE_DMA32_TRANSLATION, + .addr_mask = ~BHND_PCIE_DMA32_MASK, + .addrext_mask = BHND_PCIE_DMA32_MASK + }, + { + .base_addr = BHND_PCIE_DMA64_TRANSLATION, + .addr_mask = ~BHND_PCIE_DMA64_MASK, + .addrext_mask = 0 + }, + BHND_DMA_TRANSLATION_TABLE_END + } }; /** * PCI_V2 hardware configuration. * * Applies to: * - PCIE (cid=0x820) with ChipCommon (revision >= 32) */ static const struct bhndb_hwcfg bhndb_pci_hwcfg_v2 = { .resource_specs = (const struct resource_spec[]) { { SYS_RES_MEMORY, PCIR_BAR(0), RF_ACTIVE }, { -1, 0, 0 } }, .register_windows = (const struct bhndb_regwin[]) { /* bar0+0x0000: configurable backplane window */ { .win_type = BHNDB_REGWIN_T_DYN, .win_offset = BHNDB_PCI_V2_BAR0_WIN0_OFFSET, .win_size = BHNDB_PCI_V2_BAR0_WIN0_SIZE, .d.dyn = { .cfg_offset = BHNDB_PCI_V2_BAR0_WIN0_CONTROL, }, .res = { SYS_RES_MEMORY, PCIR_BAR(0) } }, /* bar0+0x1000: configurable backplane window */ { .win_type = BHNDB_REGWIN_T_DYN, .win_offset = BHNDB_PCI_V2_BAR0_WIN1_OFFSET, .win_size = BHNDB_PCI_V2_BAR0_WIN1_SIZE, .d.dyn = { .cfg_offset = BHNDB_PCI_V2_BAR0_WIN1_CONTROL, }, .res = { SYS_RES_MEMORY, PCIR_BAR(0) } }, /* bar0+0x2000: pcie core registers */ { .win_type = BHNDB_REGWIN_T_CORE, .win_offset = BHNDB_PCI_V2_BAR0_PCIREG_OFFSET, .win_size = BHNDB_PCI_V2_BAR0_PCIREG_SIZE, .d.core = { .class = BHND_DEVCLASS_PCIE, .unit = 0, .port = 0, .region = 0, .port_type = BHND_PORT_DEVICE }, .res = { SYS_RES_MEMORY, PCIR_BAR(0) } }, /* bar0+0x3000: chipc core registers */ { .win_type = BHNDB_REGWIN_T_CORE, .win_offset = BHNDB_PCI_V2_BAR0_CCREGS_OFFSET, .win_size = BHNDB_PCI_V2_BAR0_CCREGS_SIZE, .d.core = { .class = BHND_DEVCLASS_CC, .unit = 0, .port = 0, .region = 0, .port_type = BHND_PORT_DEVICE }, .res = { SYS_RES_MEMORY, PCIR_BAR(0) } }, BHNDB_REGWIN_TABLE_END }, + + .dma_translations = (const struct bhnd_dma_translation[]) { + { + .base_addr = BHND_PCIE_DMA32_TRANSLATION, + .addr_mask = ~BHND_PCIE_DMA32_MASK, + .addrext_mask = BHND_PCIE_DMA32_MASK + }, + { + .base_addr = BHND_PCIE_DMA64_TRANSLATION, + .addr_mask = ~BHND_PCIE_DMA64_MASK, + .addrext_mask = 0 + }, + BHND_DMA_TRANSLATION_TABLE_END + } }; /** * PCI_V3 hardware configuration. * * Applies to: * - PCIE2 (cid=0x83c) */ static const struct bhndb_hwcfg bhndb_pci_hwcfg_v3 = { .resource_specs = (const struct resource_spec[]) { { SYS_RES_MEMORY, PCIR_BAR(0), RF_ACTIVE }, { -1, 0, 0 } }, .register_windows = (const struct bhndb_regwin[]) { /* bar0+0x0000: configurable backplane window */ { .win_type = BHNDB_REGWIN_T_DYN, .win_offset = BHNDB_PCI_V3_BAR0_WIN0_OFFSET, .win_size = BHNDB_PCI_V3_BAR0_WIN0_SIZE, .d.dyn = { .cfg_offset = BHNDB_PCI_V3_BAR0_WIN0_CONTROL, }, .res = { SYS_RES_MEMORY, PCIR_BAR(0) } }, /* bar0+0x1000: configurable backplane window */ { .win_type = BHNDB_REGWIN_T_DYN, .win_offset = BHNDB_PCI_V3_BAR0_WIN1_OFFSET, .win_size = BHNDB_PCI_V3_BAR0_WIN1_SIZE, .d.dyn = { .cfg_offset = BHNDB_PCI_V3_BAR0_WIN1_CONTROL, }, .res = { SYS_RES_MEMORY, PCIR_BAR(0) } }, /* bar0+0x2000: pcie core registers */ { .win_type = BHNDB_REGWIN_T_CORE, .win_offset = BHNDB_PCI_V3_BAR0_PCIREG_OFFSET, .win_size = BHNDB_PCI_V3_BAR0_PCIREG_SIZE, .d.core = { .class = BHND_DEVCLASS_PCIE, .unit = 0, .port = 0, .region = 0, .port_type = BHND_PORT_DEVICE }, .res = { SYS_RES_MEMORY, PCIR_BAR(0) } }, /* bar0+0x3000: chipc core registers */ { .win_type = BHNDB_REGWIN_T_CORE, .win_offset = BHNDB_PCI_V3_BAR0_CCREGS_OFFSET, .win_size = BHNDB_PCI_V3_BAR0_CCREGS_SIZE, .d.core = { .class = BHND_DEVCLASS_CC, .unit = 0, .port = 0, .region = 0, .port_type = BHND_PORT_DEVICE }, .res = { SYS_RES_MEMORY, PCIR_BAR(0) } }, BHNDB_REGWIN_TABLE_END }, + + .dma_translations = (const struct bhnd_dma_translation[]) { + { + .base_addr = BHND_PCIE2_DMA64_TRANSLATION, + .addr_mask = ~BHND_PCIE2_DMA64_MASK, + .addrext_mask = 0 + }, + BHND_DMA_TRANSLATION_TABLE_END + } }; Index: head/sys/dev/bhnd/bhndb/bhndb_subr.c =================================================================== --- head/sys/dev/bhnd/bhndb/bhndb_subr.c (revision 326079) +++ head/sys/dev/bhnd/bhndb/bhndb_subr.c (revision 326080) @@ -1,1379 +1,1501 @@ /*- * Copyright (c) 2015-2016 Landon Fuller * 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: * 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include "bhndb_private.h" #include "bhndbvar.h" +static int bhndb_dma_tag_create(device_t dev, bus_dma_tag_t parent_dmat, + const struct bhnd_dma_translation *translation, + bus_dma_tag_t *dmat); + /** * Attach a BHND bridge device to @p parent. * * @param parent A parent PCI device. * @param[out] bhndb On success, the probed and attached bhndb bridge device. * @param unit The device unit number, or -1 to select the next available unit * number. * * @retval 0 success * @retval non-zero Failed to attach the bhndb device. */ int bhndb_attach_bridge(device_t parent, device_t *bhndb, int unit) { int error; *bhndb = device_add_child(parent, "bhndb", unit); if (*bhndb == NULL) return (ENXIO); if (!(error = device_probe_and_attach(*bhndb))) return (0); if ((device_delete_child(parent, *bhndb))) device_printf(parent, "failed to detach bhndb child\n"); return (error); } /* * Call BHNDB_SUSPEND_RESOURCE() for all resources in @p rl. */ static void bhndb_do_suspend_resources(device_t dev, struct resource_list *rl) { struct resource_list_entry *rle; /* Suspend all child resources. */ STAILQ_FOREACH(rle, rl, link) { /* Skip non-allocated resources */ if (rle->res == NULL) continue; BHNDB_SUSPEND_RESOURCE(device_get_parent(dev), dev, rle->type, rle->res); } } /** * Helper function for implementing BUS_RESUME_CHILD() on bridged * bhnd(4) buses. * * This implementation of BUS_RESUME_CHILD() uses BUS_GET_RESOURCE_LIST() * to find the child's resources and call BHNDB_SUSPEND_RESOURCE() for all * child resources, ensuring that the device's allocated bridge resources * will be available to other devices during bus resumption. * * Before suspending any resources, @p child is suspended by * calling bhnd_generic_suspend_child(). * * If @p child is not a direct child of @p dev, suspension is delegated to * the @p dev parent. */ int bhnd_generic_br_suspend_child(device_t dev, device_t child) { struct resource_list *rl; int error; if (device_get_parent(child) != dev) BUS_SUSPEND_CHILD(device_get_parent(dev), child); if (device_is_suspended(child)) return (EBUSY); /* Suspend the child device */ if ((error = bhnd_generic_suspend_child(dev, child))) return (error); /* Fetch the resource list. If none, there's nothing else to do */ rl = BUS_GET_RESOURCE_LIST(device_get_parent(child), child); if (rl == NULL) return (0); /* Suspend all child resources. */ bhndb_do_suspend_resources(dev, rl); return (0); } /** * Helper function for implementing BUS_RESUME_CHILD() on bridged * bhnd(4) bus devices. * * This implementation of BUS_RESUME_CHILD() uses BUS_GET_RESOURCE_LIST() * to find the child's resources and call BHNDB_RESUME_RESOURCE() for all * child resources, before delegating to bhnd_generic_resume_child(). * * If resource resumption fails, @p child will not be resumed. * * If @p child is not a direct child of @p dev, suspension is delegated to * the @p dev parent. */ int bhnd_generic_br_resume_child(device_t dev, device_t child) { struct resource_list *rl; struct resource_list_entry *rle; int error; if (device_get_parent(child) != dev) BUS_RESUME_CHILD(device_get_parent(dev), child); if (!device_is_suspended(child)) return (EBUSY); /* Fetch the resource list. If none, there's nothing else to do */ rl = BUS_GET_RESOURCE_LIST(device_get_parent(child), child); if (rl == NULL) return (bhnd_generic_resume_child(dev, child)); /* Resume all resources */ STAILQ_FOREACH(rle, rl, link) { /* Skip non-allocated resources */ if (rle->res == NULL) continue; error = BHNDB_RESUME_RESOURCE(device_get_parent(dev), dev, rle->type, rle->res); if (error) { /* Put all resources back into a suspend state */ bhndb_do_suspend_resources(dev, rl); return (error); } } /* Now that all resources are resumed, resume child */ if ((error = bhnd_generic_resume_child(dev, child))) { /* Put all resources back into a suspend state */ bhndb_do_suspend_resources(dev, rl); } return (error); } /** * Find a host resource of @p type that maps the given range. * * @param hr The resource state to search. * @param type The resource type to search for (see SYS_RES_*). * @param start The start address of the range to search for. * @param count The size of the range to search for. * * @retval resource the host resource containing the requested range. * @retval NULL if no resource containing the requested range can be found. */ struct resource * bhndb_host_resource_for_range(struct bhndb_host_resources *hr, int type, rman_res_t start, rman_res_t count) { for (u_int i = 0; hr->resource_specs[i].type != -1; i++) { struct resource *r = hr->resources[i]; if (hr->resource_specs[i].type != type) continue; /* Verify range */ if (rman_get_start(r) > start) continue; if (rman_get_end(r) < (start + count - 1)) continue; return (r); } return (NULL); } /** * Find a host resource of that matches the given register window definition. * * @param hr The resource state to search. * @param win A register window definition. * * @retval resource the host resource corresponding to @p win. * @retval NULL if no resource corresponding to @p win can be found. */ struct resource * bhndb_host_resource_for_regwin(struct bhndb_host_resources *hr, const struct bhndb_regwin *win) { const struct resource_spec *rspecs; rspecs = hr->resource_specs; for (u_int i = 0; rspecs[i].type != -1; i++) { if (win->res.type != rspecs[i].type) continue; if (win->res.rid != rspecs[i].rid) continue; /* Found declared resource */ return (hr->resources[i]); } device_printf(hr->owner, "missing regwin resource spec " "(type=%d, rid=%d)\n", win->res.type, win->res.rid); return (NULL); } /** * Allocate and initialize a new resource state structure. * * @param dev The bridge device. * @param parent_dev The parent device from which host resources should be * allocated. * @param cfg The hardware configuration to be used. */ struct bhndb_resources * bhndb_alloc_resources(device_t dev, device_t parent_dev, const struct bhndb_hwcfg *cfg) { struct bhndb_resources *r; const struct bhndb_regwin *win; bus_size_t last_window_size; int rnid; int error; bool free_ht_mem, free_br_mem, free_br_irq; free_ht_mem = false; free_br_mem = false; free_br_irq = false; r = malloc(sizeof(*r), M_BHND, M_NOWAIT|M_ZERO); if (r == NULL) return (NULL); /* Basic initialization */ r->dev = dev; r->cfg = cfg; r->res = NULL; r->min_prio = BHNDB_PRIORITY_NONE; STAILQ_INIT(&r->bus_regions); STAILQ_INIT(&r->bus_intrs); /* Initialize host address space resource manager. */ r->ht_mem_rman.rm_start = 0; r->ht_mem_rman.rm_end = ~0; r->ht_mem_rman.rm_type = RMAN_ARRAY; r->ht_mem_rman.rm_descr = "BHNDB host memory"; if ((error = rman_init(&r->ht_mem_rman))) { device_printf(r->dev, "could not initialize ht_mem_rman\n"); goto failed; } free_ht_mem = true; /* Initialize resource manager for the bridged address space. */ r->br_mem_rman.rm_start = 0; r->br_mem_rman.rm_end = BUS_SPACE_MAXADDR_32BIT; r->br_mem_rman.rm_type = RMAN_ARRAY; r->br_mem_rman.rm_descr = "BHNDB bridged memory"; if ((error = rman_init(&r->br_mem_rman))) { device_printf(r->dev, "could not initialize br_mem_rman\n"); goto failed; } free_br_mem = true; error = rman_manage_region(&r->br_mem_rman, 0, BUS_SPACE_MAXADDR_32BIT); if (error) { device_printf(r->dev, "could not configure br_mem_rman\n"); goto failed; } /* 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, BHNDB_REGWIN_T_DYN); if (r->dwa_count >= INT_MAX) { device_printf(r->dev, "max dynamic regwin count exceeded\n"); goto failed; } /* Allocate the dynamic window allocation table. */ r->dw_alloc = malloc(sizeof(r->dw_alloc[0]) * r->dwa_count, M_BHND, M_NOWAIT); if (r->dw_alloc == NULL) goto failed; /* Allocate the dynamic window allocation freelist */ r->dwa_freelist = bit_alloc(r->dwa_count, M_BHND, M_NOWAIT); if (r->dwa_freelist == NULL) goto failed; /* Initialize the dynamic window table */ rnid = 0; last_window_size = 0; for (win = cfg->register_windows; win->win_type != BHNDB_REGWIN_T_INVALID; win++) { struct bhndb_dw_alloc *dwa; /* Skip non-DYN windows */ if (win->win_type != BHNDB_REGWIN_T_DYN) continue; /* Validate the window size */ if (win->win_size == 0) { device_printf(r->dev, "ignoring zero-length dynamic " "register window\n"); continue; } else if (last_window_size == 0) { last_window_size = win->win_size; } else if (last_window_size != win->win_size) { /* * No existing hardware should trigger this. * * If you run into this in the future, the dynamic * window allocator and the resource priority system * will need to be extended to support multiple register * window allocation pools. */ device_printf(r->dev, "devices that vend multiple " "dynamic register window sizes are not currently " "supported\n"); goto failed; } dwa = &r->dw_alloc[rnid]; dwa->win = win; dwa->parent_res = NULL; dwa->rnid = rnid; dwa->target = 0x0; LIST_INIT(&dwa->refs); rnid++; } /* Allocate host resources */ - error = bhndb_alloc_host_resources(parent_dev, r->cfg, &r->res); + error = bhndb_alloc_host_resources(&r->res, dev, parent_dev, r->cfg); if (error) { device_printf(r->dev, "could not allocate host resources on %s: %d\n", device_get_nameunit(parent_dev), error); goto failed; } /* Populate (and validate) parent resource references for all * dynamic windows */ for (size_t i = 0; i < r->dwa_count; i++) { struct bhndb_dw_alloc *dwa; const struct bhndb_regwin *win; dwa = &r->dw_alloc[i]; win = dwa->win; /* Find and validate corresponding resource. */ dwa->parent_res = bhndb_host_resource_for_regwin(r->res, win); if (dwa->parent_res == NULL) { device_printf(r->dev, "no host resource found for %u " "register window with offset %#jx and " "size %#jx\n", win->win_type, (uintmax_t)win->win_offset, (uintmax_t)win->win_size); error = ENXIO; goto failed; } if (rman_get_size(dwa->parent_res) < win->win_offset + win->win_size) { device_printf(r->dev, "resource %d too small for " "register window with offset %llx and size %llx\n", rman_get_rid(dwa->parent_res), (unsigned long long) win->win_offset, (unsigned long long) win->win_size); error = EINVAL; goto failed; } } /* Add allocated memory resources to our host memory resource manager */ for (u_int i = 0; r->res->resource_specs[i].type != -1; i++) { struct resource *res; /* skip non-memory resources */ if (r->res->resource_specs[i].type != SYS_RES_MEMORY) continue; /* add host resource to set of managed regions */ res = r->res->resources[i]; error = rman_manage_region(&r->ht_mem_rman, rman_get_start(res), rman_get_end(res)); if (error) { device_printf(r->dev, "could not register host memory region with " "ht_mem_rman: %d\n", error); goto failed; } } return (r); failed: if (free_ht_mem) rman_fini(&r->ht_mem_rman); if (free_br_mem) rman_fini(&r->br_mem_rman); if (free_br_irq) rman_fini(&r->br_irq_rman); if (r->dw_alloc != NULL) free(r->dw_alloc, M_BHND); if (r->dwa_freelist != NULL) free(r->dwa_freelist, M_BHND); if (r->res != NULL) bhndb_release_host_resources(r->res); free(r, M_BHND); return (NULL); } /** + * Create a new DMA tag for the given @p translation. + * + * @param dev The bridge device. + * @param parent_dmat The parent DMA tag, or NULL if none. + * @param translation The DMA translation for which a DMA tag will + * be created. + * @param[out] dmat On success, the newly created DMA tag. + * + * @retval 0 success + * @retval non-zero if creating the new DMA tag otherwise fails, a regular + * unix error code will be returned. + */ +static int +bhndb_dma_tag_create(device_t dev, bus_dma_tag_t parent_dmat, + const struct bhnd_dma_translation *translation, bus_dma_tag_t *dmat) +{ + bus_dma_tag_t translation_tag; + bhnd_addr_t dt_mask; + bus_addr_t boundary; + bus_addr_t lowaddr, highaddr; + int error; + + highaddr = BUS_SPACE_MAXADDR; + boundary = 0; + + /* Determine full addressable mask */ + dt_mask = (translation->addr_mask | translation->addrext_mask); + KASSERT(dt_mask != 0, ("DMA addr_mask invalid: %#jx", + (uintmax_t)dt_mask)); + + /* (addr_mask|addrext_mask) is our maximum supported address */ + lowaddr = MIN(dt_mask, BUS_SPACE_MAXADDR); + + /* Do we need to to avoid crossing a DMA translation window boundary? */ + if (translation->addr_mask < BUS_SPACE_MAXADDR) { + /* round down to nearest power of two */ + boundary = translation->addr_mask & (~1ULL); + } + + /* Create our DMA tag */ + error = bus_dma_tag_create(parent_dmat, + 1, /* alignment */ + boundary, lowaddr, highaddr, + NULL, NULL, /* filter, filterarg */ + BUS_SPACE_MAXSIZE, 0, /* maxsize, nsegments */ + BUS_SPACE_MAXSIZE, 0, /* maxsegsize, flags */ + NULL, NULL, /* lockfunc, lockarg */ + &translation_tag); + if (error) { + device_printf(dev, "failed to create bridge DMA tag: %d\n", + error); + return (error); + } + + *dmat = translation_tag; + return (0); +} + +/** * Deallocate the given bridge resource structure and any associated resources. * * @param br Resource state to be deallocated. */ void bhndb_free_resources(struct bhndb_resources *br) { struct bhndb_region *region, *r_next; struct bhndb_dw_alloc *dwa; struct bhndb_dw_rentry *dwr, *dwr_next; struct bhndb_intr_handler *ih; bool leaked_regions, leaked_intrs; leaked_regions = false; leaked_intrs = false; /* No window regions may still be held */ if (!bhndb_dw_all_free(br)) { for (int i = 0; i < br->dwa_count; i++) { dwa = &br->dw_alloc[i]; /* Skip free dynamic windows */ if (bhndb_dw_is_free(br, dwa)) continue; 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); /* Clean up resource reservations */ for (size_t i = 0; i < br->dwa_count; i++) { dwa = &br->dw_alloc[i]; LIST_FOREACH_SAFE(dwr, &dwa->refs, dw_link, dwr_next) { LIST_REMOVE(dwr, dw_link); free(dwr, M_BHND); } } /* Release bus regions */ STAILQ_FOREACH_SAFE(region, &br->bus_regions, link, r_next) { STAILQ_REMOVE(&br->bus_regions, region, bhndb_region, link); free(region, M_BHND); } /* 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); } /** * Allocate host bus resources defined by @p hwcfg. * * On success, the caller assumes ownership of the allocated host resources, * which must be freed via bhndb_release_host_resources(). * - * @param dev The device to be used when allocating resources - * (e.g. via bus_alloc_resources()). + * @param[out] resources On success, the allocated host resources. + * @param dev The bridge device. + * @param parent_dev The parent device from which host resources + * should be allocated (e.g. via + * bus_alloc_resources()). * @param hwcfg The hardware configuration defining the host * resources to be allocated - * @param[out] resources On success, the allocated host resources. */ int -bhndb_alloc_host_resources(device_t dev, const struct bhndb_hwcfg *hwcfg, - struct bhndb_host_resources **resources) +bhndb_alloc_host_resources(struct bhndb_host_resources **resources, + device_t dev, device_t parent_dev, const struct bhndb_hwcfg *hwcfg) { - struct bhndb_host_resources *hr; - size_t nres; - int error; + struct bhndb_host_resources *hr; + const struct bhnd_dma_translation *dt; + bus_dma_tag_t parent_dmat; + size_t nres, ndt; + int error; + parent_dmat = bus_get_dma_tag(parent_dev); + hr = malloc(sizeof(*hr), M_BHND, M_WAITOK); - hr->owner = dev; + hr->owner = parent_dev; hr->cfg = hwcfg; hr->resource_specs = NULL; hr->resources = NULL; + hr->dma_tags = NULL; + hr->num_dma_tags = 0; /* Determine our bridge resource count from the hardware config. */ nres = 0; for (size_t i = 0; hwcfg->resource_specs[i].type != -1; i++) nres++; + /* Determine the total count and validate our DMA translation table. */ + ndt = 0; + for (dt = hwcfg->dma_translations; dt != NULL && + !BHND_DMA_IS_TRANSLATION_TABLE_END(dt); dt++) + { + /* Validate the defined translation */ + if ((dt->base_addr & dt->addr_mask) != 0) { + device_printf(dev, "invalid DMA translation; base " + "address %#jx overlaps address mask %#jx", + (uintmax_t)dt->base_addr, (uintmax_t)dt->addr_mask); + + error = EINVAL; + goto failed; + } + + if ((dt->addrext_mask & dt->addr_mask) != 0) { + device_printf(dev, "invalid DMA translation; addrext " + "mask %#jx overlaps address mask %#jx", + (uintmax_t)dt->addrext_mask, + (uintmax_t)dt->addr_mask); + + error = EINVAL; + goto failed; + } + + /* Increment our entry count */ + ndt++; + } + + /* Allocate our DMA tags */ + hr->dma_tags = malloc(sizeof(*hr->dma_tags) * ndt, M_BHND, + M_WAITOK|M_ZERO); + for (size_t i = 0; i < ndt; i++) { + error = bhndb_dma_tag_create(dev, parent_dmat, + &hwcfg->dma_translations[i], &hr->dma_tags[i]); + if (error) + goto failed; + + hr->num_dma_tags++; + } + /* Allocate space for a non-const copy of our resource_spec * table; this will be updated with the RIDs assigned by * bus_alloc_resources. */ hr->resource_specs = malloc(sizeof(hr->resource_specs[0]) * (nres + 1), M_BHND, M_WAITOK); /* Initialize and terminate the table */ for (size_t i = 0; i < nres; i++) hr->resource_specs[i] = hwcfg->resource_specs[i]; hr->resource_specs[nres].type = -1; /* Allocate space for our resource references */ hr->resources = malloc(sizeof(hr->resources[0]) * nres, M_BHND, M_WAITOK); /* Allocate host resources */ error = bus_alloc_resources(hr->owner, hr->resource_specs, hr->resources); if (error) { device_printf(dev, "could not allocate bridge resources via " - "%s: %d\n", device_get_nameunit(dev), error); + "%s: %d\n", device_get_nameunit(parent_dev), error); goto failed; } *resources = hr; return (0); failed: if (hr->resource_specs != NULL) free(hr->resource_specs, M_BHND); if (hr->resources != NULL) free(hr->resources, M_BHND); + for (size_t i = 0; i < hr->num_dma_tags; i++) + bus_dma_tag_destroy(hr->dma_tags[i]); + + if (hr->dma_tags != NULL) + free(hr->dma_tags, M_BHND); + free(hr, M_BHND); return (error); } /** * Deallocate a set of bridge host resources. * * @param hr The resources to be freed. */ void bhndb_release_host_resources(struct bhndb_host_resources *hr) { bus_release_resources(hr->owner, hr->resource_specs, hr->resources); + for (size_t i = 0; i < hr->num_dma_tags; i++) + bus_dma_tag_destroy(hr->dma_tags[i]); + free(hr->resources, M_BHND); free(hr->resource_specs, M_BHND); + free(hr->dma_tags, M_BHND); free(hr, M_BHND); } /** * Search @p cores for the core serving as the bhnd host bridge. * * This function uses a heuristic valid on all known PCI/PCIe/PCMCIA-bridged * bhnd(4) devices to determine the hostb core: * * - The core must have a Broadcom vendor ID. * - The core devclass must match the bridge type. * - The core must be the first device on the bus with the bridged device * class. * * @param cores The core table to search. * @param ncores The number of cores in @p cores. * @param bridge_devclass The expected device class of the bridge core. * @param[out] core If found, the matching host bridge core info. * * @retval 0 success * @retval ENOENT not found */ int bhndb_find_hostb_core(struct bhnd_core_info *cores, u_int ncores, bhnd_devclass_t bridge_devclass, struct bhnd_core_info *core) { struct bhnd_core_match md; struct bhnd_core_info *match; u_int match_core_idx; /* Set up a match descriptor for the required device class. */ md = (struct bhnd_core_match) { BHND_MATCH_CORE_CLASS(bridge_devclass), BHND_MATCH_CORE_UNIT(0) }; /* Find the matching core with the lowest core index */ match = NULL; match_core_idx = UINT_MAX; for (u_int i = 0; i < ncores; i++) { if (!bhnd_core_matches(&cores[i], &md)) continue; /* Lower core indices take precedence */ if (match != NULL && match_core_idx < match->core_idx) continue; match = &cores[i]; match_core_idx = match->core_idx; } if (match == NULL) return (ENOENT); *core = *match; return (0); } /** * 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. * * @param br The resource state to which the bus region entry will be added. * @param addr The base address of this region. * @param size The size of this region. * @param priority The resource priority to be assigned to allocations * made within this bus region. * @param static_regwin If available, a static register window mapping this * bus region entry. If not available, NULL. * * @retval 0 success * @retval non-zero if adding the bus region fails. */ int bhndb_add_resource_region(struct bhndb_resources *br, bhnd_addr_t addr, bhnd_size_t size, bhndb_priority_t priority, const struct bhndb_regwin *static_regwin) { struct bhndb_region *reg; /* Insert in the bus resource list */ reg = malloc(sizeof(*reg), M_BHND, M_NOWAIT); if (reg == NULL) return (ENOMEM); *reg = (struct bhndb_region) { .addr = addr, .size = size, .priority = priority, .static_regwin = static_regwin }; STAILQ_INSERT_HEAD(&br->bus_regions, reg, link); return (0); } /** * Find the bus region that maps @p size bytes at @p addr. * * @param br The resource state to search. * @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. */ struct bhndb_region * bhndb_find_resource_region(struct bhndb_resources *br, bhnd_addr_t addr, bhnd_size_t size) { struct bhndb_region *region; STAILQ_FOREACH(region, &br->bus_regions, link) { /* Request must fit within the region's mapping */ if (addr < region->addr) continue; if (addr + size > region->addr + region->size) continue; return (region); } /* Not found */ return (NULL); } /** * Find the entry matching @p r in @p dwa's references, if any. * * @param dwa The dynamic window allocation to search * @param r The resource to search for in @p dwa. */ static struct bhndb_dw_rentry * bhndb_dw_find_resource_entry(struct bhndb_dw_alloc *dwa, struct resource *r) { struct bhndb_dw_rentry *rentry; LIST_FOREACH(rentry, &dwa->refs, dw_link) { struct resource *dw_res = rentry->dw_res; /* Match dev/rid/addr/size */ if (rman_get_device(dw_res) != rman_get_device(r) || rman_get_rid(dw_res) != rman_get_rid(r) || rman_get_start(dw_res) != rman_get_start(r) || rman_get_size(dw_res) != rman_get_size(r)) { continue; } /* Matching allocation found */ return (rentry); } return (NULL); } /** * Find the dynamic region allocated for @p r, if any. * * @param br The resource state to search. * @param r The resource to search for. * * @retval bhndb_dw_alloc The allocation record for @p r. * @retval NULL if no dynamic window is allocated for @p r. */ struct bhndb_dw_alloc * bhndb_dw_find_resource(struct bhndb_resources *br, struct resource *r) { struct bhndb_dw_alloc *dwa; for (size_t i = 0; i < br->dwa_count; i++) { dwa = &br->dw_alloc[i]; /* Skip free dynamic windows */ if (bhndb_dw_is_free(br, dwa)) continue; /* Matching allocation found? */ if (bhndb_dw_find_resource_entry(dwa, r) != NULL) return (dwa); } return (NULL); } /** * Find an existing dynamic window mapping @p size bytes * at @p addr. The window may or may not be free. * * @param br The resource state to search. * @param addr The requested starting address. * @param size The requested size. * * @retval bhndb_dw_alloc A window allocation that fully contains the requested * range. * @retval NULL If no mapping region can be found. */ struct bhndb_dw_alloc * bhndb_dw_find_mapping(struct bhndb_resources *br, bhnd_addr_t addr, bhnd_size_t size) { struct bhndb_dw_alloc *dwr; const struct bhndb_regwin *win; /* Search for an existing dynamic mapping of this address range. */ for (size_t i = 0; i < br->dwa_count; i++) { dwr = &br->dw_alloc[i]; win = dwr->win; /* Verify the range */ if (addr < dwr->target) continue; if (addr + size > dwr->target + win->win_size) continue; /* Found a usable mapping */ return (dwr); } /* not found */ return (NULL); } /** * Retain a reference to @p dwa for use by @p res. * * @param br The resource state owning @p dwa. * @param dwa The allocation record to be retained. * @param res The resource that will own a reference to @p dwa. * * @retval 0 success * @retval ENOMEM Failed to allocate a new reference structure. */ int bhndb_dw_retain(struct bhndb_resources *br, struct bhndb_dw_alloc *dwa, struct resource *res) { struct bhndb_dw_rentry *rentry; KASSERT(bhndb_dw_find_resource_entry(dwa, res) == NULL, ("double-retain of dynamic window for same resource")); /* Insert a reference entry; we use M_NOWAIT to allow use from * within a non-sleepable lock */ rentry = malloc(sizeof(*rentry), M_BHND, M_NOWAIT); if (rentry == NULL) return (ENOMEM); rentry->dw_res = res; LIST_INSERT_HEAD(&dwa->refs, rentry, dw_link); /* Update the free list */ bit_set(br->dwa_freelist, dwa->rnid); return (0); } /** * Release a reference to @p dwa previously retained by @p res. If the * reference count of @p dwa reaches zero, it will be added to the * free list. * * @param br The resource state owning @p dwa. * @param dwa The allocation record to be released. * @param res The resource that currently owns a reference to @p dwa. */ void bhndb_dw_release(struct bhndb_resources *br, struct bhndb_dw_alloc *dwa, struct resource *r) { struct bhndb_dw_rentry *rentry; /* Find the rentry */ rentry = bhndb_dw_find_resource_entry(dwa, r); KASSERT(rentry != NULL, ("over release of resource entry")); LIST_REMOVE(rentry, dw_link); free(rentry, M_BHND); /* If this was the last reference, update the free list */ if (LIST_EMPTY(&dwa->refs)) bit_clear(br->dwa_freelist, dwa->rnid); } /** * Attempt to set (or reset) the target address of @p dwa to map @p size bytes * at @p addr. * * This will apply any necessary window alignment and verify that * the window is capable of mapping the requested range prior to modifying * therecord. * * @param dev The device on which to issue the BHNDB_SET_WINDOW_ADDR() request. * @param br The resource state owning @p dwa. * @param dwa The allocation record to be configured. * @param addr The address to be mapped via @p dwa. * @param size The number of bytes to be mapped at @p addr. * * @retval 0 success * @retval non-zero no usable register window available. */ int bhndb_dw_set_addr(device_t dev, struct bhndb_resources *br, struct bhndb_dw_alloc *dwa, bus_addr_t addr, bus_size_t size) { const struct bhndb_regwin *rw; bus_addr_t offset; int error; rw = dwa->win; KASSERT(bhndb_dw_is_free(br, dwa), ("attempting to set the target address on an in-use window")); /* Page-align the target address */ offset = addr % rw->win_size; dwa->target = addr - offset; /* Verify that the window is large enough for the full target */ if (rw->win_size - offset < size) return (ENOMEM); /* Update the window target */ error = BHNDB_SET_WINDOW_ADDR(dev, dwa->win, dwa->target); if (error) { dwa->target = 0x0; return (error); } return (0); } /** * Return the count of @p type register windows in @p table. * * @param table The table to search. * @param type The required window type, or BHNDB_REGWIN_T_INVALID to * count all register window types. */ size_t bhndb_regwin_count(const struct bhndb_regwin *table, bhndb_regwin_type_t type) { const struct bhndb_regwin *rw; size_t count; count = 0; for (rw = table; rw->win_type != BHNDB_REGWIN_T_INVALID; rw++) { if (type == BHNDB_REGWIN_T_INVALID || rw->win_type == type) count++; } return (count); } /** * Search @p table for the first window with the given @p type. * * @param table The table to search. * @param type The required window type. * @param min_size The minimum window size. * * @retval bhndb_regwin The first matching window. * @retval NULL If no window of the requested type could be found. */ const struct bhndb_regwin * bhndb_regwin_find_type(const struct bhndb_regwin *table, bhndb_regwin_type_t type, bus_size_t min_size) { const struct bhndb_regwin *rw; for (rw = table; rw->win_type != BHNDB_REGWIN_T_INVALID; rw++) { if (rw->win_type == type && rw->win_size >= min_size) return (rw); } return (NULL); } /** * Search @p windows for the first matching core window. * * @param table The table to search. * @param class The required core class. * @param unit The required core unit, or -1. * @param port_type The required port type. * @param port The required port. * @param region The required region. * * @retval bhndb_regwin The first matching window. * @retval NULL If no matching window was found. */ const struct bhndb_regwin * bhndb_regwin_find_core(const struct bhndb_regwin *table, bhnd_devclass_t class, int unit, bhnd_port_type port_type, u_int port, u_int region) { const struct bhndb_regwin *rw; for (rw = table; rw->win_type != BHNDB_REGWIN_T_INVALID; rw++) { if (rw->win_type != BHNDB_REGWIN_T_CORE) continue; if (rw->d.core.class != class) continue; if (unit != -1 && rw->d.core.unit != unit) continue; if (rw->d.core.port_type != port_type) continue; if (rw->d.core.port != port) continue; if (rw->d.core.region != region) continue; return (rw); } return (NULL); } /** * Search @p windows for the best available window of at least @p min_size. * * Search order: * - BHND_REGWIN_T_CORE * - BHND_REGWIN_T_DYN * * @param table The table to search. * @param class The required core class. * @param unit The required core unit, or -1. * @param port_type The required port type. * @param port The required port. * @param region The required region. * @param min_size The minimum window size. * * @retval bhndb_regwin The first matching window. * @retval NULL If no matching window was found. */ const struct bhndb_regwin * bhndb_regwin_find_best(const struct bhndb_regwin *table, bhnd_devclass_t class, int unit, bhnd_port_type port_type, u_int port, u_int region, bus_size_t min_size) { const struct bhndb_regwin *rw; /* Prefer a fixed core mapping */ rw = bhndb_regwin_find_core(table, class, unit, port_type, port, region); if (rw != NULL) return (rw); /* Fall back on a generic dynamic window */ return (bhndb_regwin_find_type(table, BHNDB_REGWIN_T_DYN, min_size)); } /** * Return true if @p regw defines a BHNDB_REGWIN_T_CORE register window * that matches against @p core. * * @param regw A register window to match against. * @param core The bhnd(4) core info to match against @p regw. */ bool bhndb_regwin_match_core(const struct bhndb_regwin *regw, struct bhnd_core_info *core) { /* Only core windows are supported */ if (regw->win_type != BHNDB_REGWIN_T_CORE) return (false); /* Device class must match */ if (bhnd_core_class(core) != regw->d.core.class) return (false); /* Device unit must match */ if (core->unit != regw->d.core.unit) return (false); /* Matches */ return (true); } /** * Search for a core resource priority descriptor in @p table that matches * @p core. * * @param table The table to search. * @param core The core to match against @p table. */ const struct bhndb_hw_priority * bhndb_hw_priority_find_core(const struct bhndb_hw_priority *table, struct bhnd_core_info *core) { const struct bhndb_hw_priority *hp; for (hp = table; hp->ports != NULL; hp++) { if (bhnd_core_matches(core, &hp->match)) return (hp); } /* not found */ return (NULL); } Index: head/sys/dev/bhnd/bhndb/bhndbvar.h =================================================================== --- head/sys/dev/bhnd/bhndb/bhndbvar.h (revision 326079) +++ head/sys/dev/bhnd/bhndb/bhndbvar.h (revision 326080) @@ -1,185 +1,190 @@ /*- * Copyright (c) 2015-2016 Landon Fuller * 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: * 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 _BHND_BHNDBVAR_H_ #define _BHND_BHNDBVAR_H_ #include #include #include #include #include #include #include #include #include "bhndb.h" #include "bhndb_if.h" /* * Definitions shared by bhndb(4) driver implementations. */ DECLARE_CLASS(bhndb_driver); /* forward declarations */ struct bhndb_intr_isrc; struct bhndb_resources; struct bhndb_host_resources; int bhndb_attach(device_t dev, struct bhnd_chipid *cid, struct bhnd_core_info *cores, u_int ncores, struct bhnd_core_info *bridge_core, bhnd_erom_class_t *erom_class); int bhndb_generic_probe(device_t dev); int bhndb_generic_detach(device_t dev); int bhndb_generic_suspend(device_t dev); int bhndb_generic_resume(device_t dev); int bhndb_generic_init_full_config(device_t dev, device_t child, const struct bhndb_hw_priority *hw_prio_table); int bhnd_generic_br_suspend_child(device_t dev, device_t child); int bhnd_generic_br_resume_child(device_t dev, device_t child); int bhndb_find_hostb_core( struct bhnd_core_info *cores, u_int ncores, 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); +int bhndb_alloc_host_resources( + struct bhndb_host_resources **resources, + device_t dev, device_t parent_dev, + const struct bhndb_hwcfg *hwcfg); + void bhndb_release_host_resources( struct bhndb_host_resources *resources); struct resource *bhndb_host_resource_for_range( struct bhndb_host_resources *resources, int type, rman_res_t start, rman_res_t count); struct resource *bhndb_host_resource_for_regwin( struct bhndb_host_resources *resources, const struct bhndb_regwin *win); size_t bhndb_regwin_count( const struct bhndb_regwin *table, bhndb_regwin_type_t type); const struct bhndb_regwin *bhndb_regwin_find_type( const struct bhndb_regwin *table, bhndb_regwin_type_t type, bus_size_t min_size); const struct bhndb_regwin *bhndb_regwin_find_core( const struct bhndb_regwin *table, bhnd_devclass_t class, int unit, bhnd_port_type port_type, u_int port, u_int region); const struct bhndb_regwin *bhndb_regwin_find_best( const struct bhndb_regwin *table, bhnd_devclass_t class, int unit, bhnd_port_type port_type, u_int port, u_int region, bus_size_t min_size); bool bhndb_regwin_match_core( const struct bhndb_regwin *regw, struct bhnd_core_info *core); /** * bhndb child address space. Children either operate in the bridged * SoC address space, or within the address space mapped to the host * device (e.g. the PCI BAR(s)). */ typedef enum { BHNDB_ADDRSPACE_BRIDGED, /**< bridged (SoC) address space */ BHNDB_ADDRSPACE_NATIVE /**< host address space */ } bhndb_addrspace; /** bhndb child instance state */ struct bhndb_devinfo { bhndb_addrspace addrspace; /**< child address space. */ 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. */ struct bhndb_host_resources { device_t owner; /**< device owning the allocated resources */ const struct bhndb_hwcfg *cfg; /**< bridge hardware configuration */ struct resource_spec *resource_specs; /**< resource specification table */ struct resource **resources; /**< allocated resource table */ + bus_dma_tag_t *dma_tags; /**< DMA tags for all hwcfg DMA translations, or NULL + if DMA is not supported */ + size_t num_dma_tags; /**< DMA tag count */ }; /** * bhndb driver instance state. Must be first member of all subclass * softc structures. */ struct bhndb_softc { device_t dev; /**< bridge device */ struct bhnd_chipid chipid; /**< chip identification */ struct bhnd_core_info bridge_core; /**< bridge core info */ device_t parent_dev; /**< parent device */ device_t bus_dev; /**< child bhnd(4) bus */ struct bhnd_service_registry services; /**< local service registry */ 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: head/sys/dev/bhnd/cores/pci/bhnd_pcireg.h =================================================================== --- head/sys/dev/bhnd/cores/pci/bhnd_pcireg.h (revision 326079) +++ head/sys/dev/bhnd/cores/pci/bhnd_pcireg.h (revision 326080) @@ -1,417 +1,417 @@ /*- * Copyright (c) 2015 Landon Fuller * Copyright (c) 2010 Broadcom Corporation * All rights reserved. * * This file is derived from the hndsoc.h, pci_core.h, and pcie_core.h headers * distributed with Broadcom's initial brcm80211 Linux driver release, as * contributed to the Linux staging repository. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * $FreeBSD$ */ #ifndef _BHND_CORES_PCI_BHND_PCIREG_H_ #define _BHND_CORES_PCI_BHND_PCIREG_H_ /* * PCI/PCIe-Gen1 DMA Constants */ -#define BHND_PCI_DMA32_TRANSLATION 0x40000000 /* Client Mode sb2pcitranslation2 (1 GB) */ -#define BHND_PCI_DMA32_SZ 0x40000000 /* Client Mode sb2pcitranslation2 size in bytes */ +#define BHND_PCI_DMA32_TRANSLATION 0x40000000 /**< PCI DMA32 address translation (sbtopci2) */ +#define BHND_PCI_DMA32_MASK BHND_PCI_SBTOPCI2_MASK /**< PCI DMA32 translation mask */ -#define BHND_PCIE_DMA32_TRANSLATION BHND_PCI_DMA32_TRANSLATION -#define BHND_PCIE_DMA32_SZ BHND_PCI_DMA32_SZ +#define BHND_PCIE_DMA32_TRANSLATION 0x80000000 /**< PCIe-Gen1 DMA32 address translation (sb2pcitranslation2) */ +#define BHND_PCIE_DMA32_MASK BHND_PCIE_SBTOPCI2_MASK /**< PCIe-Gen1 DMA32 translation mask */ -#define BHND_PCIE_DMA64_L32 0x00000000 /**< 64-bit client mode sb2pcitranslation2 (2 ZettaBytes, low 32 bits) */ -#define BHND_PCIE_DMA64_H32 0x80000000 /**< 64-bit client mode sb2pcitranslation2 (2 ZettaBytes, high 32 bits) */ - +#define BHND_PCIE_DMA64_TRANSLATION _BHND_PCIE_DMA64(TRANSLATION) /**< PCIe-Gen1 DMA64 address translation (sb2pcitranslation2) */ +#define BHND_PCIE_DMA64_MASK _BHND_PCIE_DMA64(MASK) /**< PCIe-Gen1 DMA64 translation mask */ +#define _BHND_PCIE_DMA64(_x) ((uint64_t)BHND_PCIE_DMA32_ ## _x << 32) /* * PCI Core Registers */ #define BHND_PCI_CTL 0x000 /**< PCI core control*/ #define BHND_PCI_ARB_CTL 0x010 /**< PCI arbiter control */ #define BHND_PCI_CLKRUN_CTL 0x014 /**< PCI clckrun control (>= rev11) */ #define BHND_PCI_INTR_STATUS 0x020 /**< Interrupt status */ #define BHND_PCI_INTR_MASK 0x024 /**< Interrupt mask */ #define BHND_PCI_SBTOPCI_MBOX 0x028 /**< Sonics to PCI mailbox */ #define BHND_PCI_BCAST_ADDR 0x050 /**< Sonics broadcast address (pci) */ #define BHND_PCI_BCAST_DATA 0x054 /**< Sonics broadcast data (pci) */ #define BHND_PCI_GPIO_IN 0x060 /**< GPIO input (>= rev2) */ #define BHND_PCI_GPIO_OUT 0x064 /**< GPIO output (>= rev2) */ #define BHND_PCI_GPIO_EN 0x068 /**< GPIO output enable (>= rev2) */ #define BHND_PCI_GPIO_CTL 0x06C /**< GPIO control (>= rev2) */ #define BHND_PCI_SBTOPCI0 0x100 /**< Sonics to PCI translation 0 */ #define BHND_PCI_SBTOPCI1 0x104 /**< Sonics to PCI translation 1 */ #define BHND_PCI_SBTOPCI2 0x108 /**< Sonics to PCI translation 2 */ #define BHND_PCI_FUNC0_CFG 0x400 /**< PCI function 0 cfg space (>= rev8) */ #define BHND_PCI_FUNC1_CFG 0x500 /**< PCI function 1 cfg space (>= rev8) */ #define BHND_PCI_FUNC2_CFG 0x600 /**< PCI function 2 cfg space (>= rev8) */ #define BHND_PCI_FUNC3_CFG 0x700 /**< PCI function 3 cfg space (>= rev8) */ #define BHND_PCI_SPROM_SHADOW 0x800 /**< PCI SPROM shadow */ /* BHND_PCI_CTL */ #define BHND_PCI_CTL_RST_OE 0x01 /* When set, drives PCI_RESET out to pin */ #define BHND_PCI_CTL_RST 0x02 /* Value driven out to pin */ #define BHND_PCI_CTL_CLK_OE 0x04 /* When set, drives clock as gated by PCI_CLK out to pin */ #define BHND_PCI_CTL_CLK 0x08 /* Gate for clock driven out to pin */ /* BHND_PCI_ARB_CTL */ #define BHND_PCI_ARB_INT 0x01 /* When set, use an internal arbiter */ #define BHND_PCI_ARB_EXT 0x02 /* When set, use an external arbiter */ /* BHND_PCI_ARB_CTL - ParkID (>= rev8) */ #define BHND_PCI_ARB_PARKID_MASK 0x1c /* Selects which agent is parked on an idle bus */ #define BHND_PCI_ARB_PARKID_SHIFT 2 #define BHND_PCI_ARB_PARKID_EXT0 0 /* External master 0 */ #define BHND_PCI_ARB_PARKID_EXT1 1 /* External master 1 */ #define BHND_PCI_ARB_PARKID_EXT2 2 /* External master 2 */ #define BHND_PCI_ARB_PARKID_EXT3 3 /* External master 3 (rev >= 11) */ #define BHND_PCI_ARB_PARKID_INT_r10 3 /* Internal master (rev < 11) */ #define BHND_PCI_ARB_PARKID_INT_r11 4 /* Internal master (rev >= 11) */ #define BHND_PCI_ARB_PARKID_LAST_r10 4 /* Last active master (rev < 11) */ #define BHND_PCI_ARB_PARKID_LAST_r11 5 /* Last active master (rev >= 11) */ /* BHND_PCI_CLKRUN_CTL */ #define BHND_PCI_CLKRUN_DSBL 0x8000 /* Bit 15 forceClkrun */ /* BHND_PCI_INTR_STATUS / BHND_PCI_INTR_MASK */ #define BHND_PCI_INTR_A 0x01 /* PCI INTA# is asserted */ #define BHND_PCI_INTR_B 0x02 /* PCI INTB# is asserted */ #define BHND_PCI_INTR_SERR 0x04 /* PCI SERR# has been asserted (write one to clear) */ #define BHND_PCI_INTR_PERR 0x08 /* PCI PERR# has been asserted (write one to clear) */ /* BHND_PCI_SBTOPCI_MBOX * (General) PCI/SB mailbox interrupts, two bits per pci function */ #define BHND_PCI_SBTOPCI_MBOX_F0_0 0x100 /* function 0, int 0 */ #define BHND_PCI_SBTOPCI_MBOX_F0_1 0x200 /* function 0, int 1 */ #define BHND_PCI_SBTOPCI_MBOX_F1_0 0x400 /* function 1, int 0 */ #define BHND_PCI_SBTOPCI_MBOX_F1_1 0x800 /* function 1, int 1 */ #define BHND_PCI_SBTOPCI_MBOX_F2_0 0x1000 /* function 2, int 0 */ #define BHND_PCI_SBTOPCI_MBOX_F2_1 0x2000 /* function 2, int 1 */ #define BHND_PCI_SBTOPCI_MBOX_F3_0 0x4000 /* function 3, int 0 */ #define BHND_PCI_SBTOPCI_MBOX_F3_1 0x8000 /* function 3, int 1 */ /* BHND_PCI_BCAST_ADDR */ #define BHNC_PCI_BCAST_ADDR_MASK 0xFF /* Broadcast register address */ /* Sonics to PCI translation types */ #define BHND_PCI_SBTOPCI0_MASK 0xfc000000 #define BHND_PCI_SBTOPCI1_MASK 0xfc000000 #define BHND_PCI_SBTOPCI2_MASK 0xc0000000 /* Access type bits (0:1) */ #define BHND_PCI_SBTOPCI_MEM 0 #define BHND_PCI_SBTOPCI_IO 1 #define BHND_PCI_SBTOPCI_CFG0 2 #define BHND_PCI_SBTOPCI_CFG1 3 #define BHND_PCI_SBTOPCI_PREF 0x4 /* prefetch enable */ #define BHND_PCI_SBTOPCI_BURST 0x8 /* burst enable */ #define BHND_PCI_SBTOPCI_RC_MASK 0x30 /* read command (>= rev11) */ #define BHND_PCI_SBTOPCI_RC_READ 0x00 /* memory read */ #define BHND_PCI_SBTOPCI_RC_READLINE 0x10 /* memory read line */ #define BHND_PCI_SBTOPCI_RC_READMULTI 0x20 /* memory read multiple */ /* PCI core index in SROM shadow area */ #define BHND_PCI_SRSH_PI_OFFSET 0 /* first word */ #define BHND_PCI_SRSH_PI_MASK 0xf000 /* bit 15:12 */ #define BHND_PCI_SRSH_PI_SHIFT 12 /* bit 15:12 */ /* * PCIe-Gen1 Core Registers */ #define BHND_PCIE_CTL BHND_PCI_CTL /**< PCI core control*/ #define BHND_PCIE_BIST_STATUS 0x00C /**< BIST status */ #define BHND_PCIE_GPIO_SEL 0x010 /**< GPIO select */ #define BHND_PCIE_GPIO_OUT_EN 0x014 /**< GPIO output enable */ #define BHND_PCIE_INTR_STATUS BHND_PCI_INTR_STATUS /**< Interrupt status */ #define BHND_PCIE_INTR_MASK BHND_PCI_INTR_MASK /**< Interrupt mask */ #define BHND_PCIE_SBTOPCI_MBOX BHND_PCI_SBTOPCI_MBOX /**< Sonics to PCI mailbox */ #define BHND_PCIE_SBTOPCI0 BHND_PCI_SBTOPCI0 /**< Sonics to PCI translation 0 */ #define BHND_PCIE_SBTOPCI1 BHND_PCI_SBTOPCI1 /**< Sonics to PCI translation 1 */ #define BHND_PCIE_SBTOPCI2 BHND_PCI_SBTOPCI2 /**< Sonics to PCI translation 2 */ /* indirect pci config space access */ #define BHND_PCIE_CFG_ADDR 0x120 /**< pcie config space address */ #define BHND_PCIE_CFG_DATA 0x124 /**< pcie config space data */ /* mdio register access */ #define BHND_PCIE_MDIO_CTL 0x128 /**< mdio control */ #define BHND_PCIE_MDIO_DATA 0x12C /**< mdio data */ /* indirect protocol phy/dllp/tlp register access */ #define BHND_PCIE_IND_ADDR 0x130 /**< internal protocol register address */ #define BHND_PCIE_IND_DATA 0x134 /**< internal protocol register data */ #define BHND_PCIE_CLKREQEN_CTL 0x138 /**< clkreq rdma control */ #define BHND_PCIE_FUNC0_CFG BHND_PCI_FUNC0_CFG /**< PCI function 0 cfg space */ #define BHND_PCIE_FUNC1_CFG BHND_PCI_FUNC1_CFG /**< PCI function 1 cfg space */ #define BHND_PCIE_FUNC2_CFG BHND_PCI_FUNC2_CFG /**< PCI function 2 cfg space */ #define BHND_PCIE_FUNC3_CFG BHND_PCI_FUNC3_CFG /**< PCI function 3 cfg space */ #define BHND_PCIE_SPROM_SHADOW BHND_PCI_SPROM_SHADOW /**< PCI SPROM shadow */ /* BHND_PCIE_CTL */ #define BHND_PCIE_CTL_RST_OE BHND_PCI_CTL_RST_OE /* When set, drives PCI_RESET out to pin */ #define BHND_PCIE_CTL_RST BHND_PCI_CTL_RST_OE /* Value driven out to pin */ /* BHND_PCI_INTR_STATUS / BHND_PCI_INTR_MASK */ #define BHND_PCIE_INTR_A BHND_PCI_INTR_A /* PCIE INTA message is received */ #define BHND_PCIE_INTR_B BHND_PCI_INTR_B /* PCIE INTB message is received */ #define BHND_PCIE_INTR_FATAL 0x04 /* PCIE INTFATAL message is received */ #define BHND_PCIE_INTR_NFATAL 0x08 /* PCIE INTNONFATAL message is received */ #define BHND_PCIE_INTR_CORR 0x10 /* PCIE INTCORR message is received */ #define BHND_PCIE_INTR_PME 0x20 /* PCIE INTPME message is received */ /* SB to PCIE translation masks */ #define BHND_PCIE_SBTOPCI0_MASK BHND_PCI_SBTOPCI0_MASK #define BHND_PCIE_SBTOPCI1_MASK BHND_PCI_SBTOPCI1_MASK #define BHND_PCIE_SBTOPCI2_MASK BHND_PCI_SBTOPCI2_MASK /* Access type bits (0:1) */ #define BHND_PCIE_SBTOPCI_MEM BHND_PCI_SBTOPCI_MEM #define BHND_PCIE_SBTOPCI_IO BHND_PCI_SBTOPCI_IO #define BHND_PCIE_SBTOPCI_CFG0 BHND_PCI_SBTOPCI_CFG0 #define BHND_PCIE_SBTOPCI_CFG1 BHND_PCI_SBTOPCI_CFG1 #define BHND_PCIE_SBTOPCI_PREF BHND_PCI_SBTOPCI_PREF /* prefetch enable */ #define BHND_PCIE_SBTOPCI_BURST BHND_PCI_SBTOPCI_BURST /* burst enable */ /* BHND_PCIE_CFG_ADDR / BHND_PCIE_CFG_DATA */ #define BHND_PCIE_CFG_ADDR_FUNC_MASK 0x7000 #define BHND_PCIE_CFG_ADDR_FUNC_SHIFT 12 #define BHND_PCIE_CFG_ADDR_REG_MASK 0x0FFF #define BHND_PCIE_CFG_ADDR_REG_SHIFT 0 #define BHND_PCIE_CFG_OFFSET(f, r) \ ((((f) & BHND_PCIE_CFG_ADDR_FUNC_MASK) << BHND_PCIE_CFG_ADDR_FUNC_SHIFT) | \ (((r) & BHND_PCIE_CFG_ADDR_FUNC_SHIFT) << BHND_PCIE_CFG_ADDR_REG_SHIFT)) /* BHND_PCIE_MDIO_CTL control */ #define BHND_PCIE_MDIOCTL_DIVISOR_MASK 0x7f /* clock divisor mask */ #define BHND_PCIE_MDIOCTL_DIVISOR_VAL 0x2 /* default clock divisor */ #define BHND_PCIE_MDIOCTL_PREAM_EN 0x80 /* enable preamble mode */ #define BHND_PCIE_MDIOCTL_DONE 0x100 /* tranaction completed */ /* PCIe BHND_PCIE_MDIO_DATA Data */ #define BHND_PCIE_MDIODATA_PHYADDR_MASK 0x0f800000 /* phy addr */ #define BHND_PCIE_MDIODATA_PHYADDR_SHIFT 23 #define BHND_PCIE_MDIODATA_REGADDR_MASK 0x007c0000 /* reg/dev addr */ #define BHND_PCIE_MDIODATA_REGADDR_SHIFT 18 #define BHND_PCIE_MDIODATA_DATA_MASK 0x0000ffff /* data */ #define BHND_PCIE_MDIODATA_TA 0x00020000 /* slave turnaround time */ #define BHND_PCIE_MDIODATA_START 0x40000000 /* start of transaction */ #define BHND_PCIE_MDIODATA_CMD_WRITE 0x10000000 /* write command */ #define BHND_PCIE_MDIODATA_CMD_READ 0x20000000 /* read command */ #define BHND_PCIE_MDIODATA_ADDR(_phyaddr, _regaddr) ( \ (((_phyaddr) << BHND_PCIE_MDIODATA_PHYADDR_SHIFT) & \ BHND_PCIE_MDIODATA_PHYADDR_MASK) | \ (((_regaddr) << BHND_PCIE_MDIODATA_REGADDR_SHIFT) & \ BHND_PCIE_MDIODATA_REGADDR_MASK) \ ) /* PCIE protocol PHY diagnostic registers */ #define BHND_PCIE_PLP_MODEREG 0x200 /* Mode */ #define BHND_PCIE_PLP_STATUSREG 0x204 /* Status */ #define BHND_PCIE_PLP_LTSSMCTRLREG 0x208 /* LTSSM control */ #define BHND_PCIE_PLP_LTLINKNUMREG 0x20c /* Link Training Link number */ #define BHND_PCIE_PLP_LTLANENUMREG 0x210 /* Link Training Lane number */ #define BHND_PCIE_PLP_LTNFTSREG 0x214 /* Link Training N_FTS */ #define BHND_PCIE_PLP_ATTNREG 0x218 /* Attention */ #define BHND_PCIE_PLP_ATTNMASKREG 0x21C /* Attention Mask */ #define BHND_PCIE_PLP_RXERRCTR 0x220 /* Rx Error */ #define BHND_PCIE_PLP_RXFRMERRCTR 0x224 /* Rx Framing Error */ #define BHND_PCIE_PLP_RXERRTHRESHREG 0x228 /* Rx Error threshold */ #define BHND_PCIE_PLP_TESTCTRLREG 0x22C /* Test Control reg */ #define BHND_PCIE_PLP_SERDESCTRLOVRDREG 0x230 /* SERDES Control Override */ #define BHND_PCIE_PLP_TIMINGOVRDREG 0x234 /* Timing param override */ #define BHND_PCIE_PLP_RXTXSMDIAGREG 0x238 /* RXTX State Machine Diag */ #define BHND_PCIE_PLP_LTSSMDIAGREG 0x23C /* LTSSM State Machine Diag */ /* PCIE protocol DLLP diagnostic registers */ #define BHND_PCIE_DLLP_LCREG 0x100 /* Link Control */ #define BHND_PCIE_DLLP_LCREG_PCIPM_EN 0x40 /* Enable PCI-PM power management */ #define BHND_PCIE_DLLP_LSREG 0x104 /* Link Status */ #define BHND_PCIE_DLLP_LAREG 0x108 /* Link Attention */ #define BHND_PCIE_DLLP_LAMASKREG 0x10C /* Link Attention Mask */ #define BHND_PCIE_DLLP_NEXTTXSEQNUMREG 0x110 /* Next Tx Seq Num */ #define BHND_PCIE_DLLP_ACKEDTXSEQNUMREG 0x114 /* Acked Tx Seq Num */ #define BHND_PCIE_DLLP_PURGEDTXSEQNUMREG 0x118 /* Purged Tx Seq Num */ #define BHND_PCIE_DLLP_RXSEQNUMREG 0x11C /* Rx Sequence Number */ #define BHND_PCIE_DLLP_LRREG 0x120 /* Link Replay */ #define BHND_PCIE_DLLP_LACKTOREG 0x124 /* Link Ack Timeout */ #define BHND_PCIE_DLLP_PMTHRESHREG 0x128 /* Power Management Threshold */ #define BHND_PCIE_L0THRESHOLDTIME_MASK 0xFF00 /* bits 0 - 7 */ #define BHND_PCIE_L1THRESHOLDTIME_MASK 0xFF00 /* bits 8 - 15 */ #define BHND_PCIE_L1THRESHOLDTIME_SHIFT 8 /* PCIE_L1THRESHOLDTIME_SHIFT */ #define BHND_PCIE_L1THRESHOLD_WARVAL 0x72 /* WAR value */ #define BHND_PCIE_ASPMTIMER_EXTEND 0x1000000 /* > rev7: enable extend ASPM timer */ #define BHND_PCIE_DLLP_RTRYWPREG 0x12C /* Retry buffer write ptr */ #define BHND_PCIE_DLLP_RTRYRPREG 0x130 /* Retry buffer Read ptr */ #define BHND_PCIE_DLLP_RTRYPPREG 0x134 /* Retry buffer Purged ptr */ #define BHND_PCIE_DLLP_RTRRWREG 0x138 /* Retry buffer Read/Write */ #define BHND_PCIE_DLLP_ECTHRESHREG 0x13C /* Error Count Threshold */ #define BHND_PCIE_DLLP_TLPERRCTRREG 0x140 /* TLP Error Counter */ #define BHND_PCIE_DLLP_ERRCTRREG 0x144 /* Error Counter */ #define BHND_PCIE_DLLP_NAKRXCTRREG 0x148 /* NAK Received Counter */ #define BHND_PCIE_DLLP_TESTREG 0x14C /* Test */ #define BHND_PCIE_DLLP_PKTBIST 0x150 /* Packet BIST */ #define BHND_PCIE_DLLP_PCIE11 0x154 /* DLLP PCIE 1.1 reg */ #define BHND_PCIE_DLLP_LSREG_LINKUP (1 << 16) /* PCIE protocol TLP diagnostic registers */ #define BHND_PCIE_TLP_CONFIGREG 0x000 /* Configuration */ #define BHND_PCIE_TLP_WORKAROUNDSREG 0x004 /* TLP Workarounds */ #define BHND_PCIE_TLP_WORKAROUND_URBIT 0x8 /* If enabled, UR status bit is set * on memory access of an unmatched * address */ #define BHND_PCIE_TLP_WRDMAUPPER 0x010 /* Write DMA Upper Address */ #define BHND_PCIE_TLP_WRDMALOWER 0x014 /* Write DMA Lower Address */ #define BHND_PCIE_TLP_WRDMAREQ_LBEREG 0x018 /* Write DMA Len/ByteEn Req */ #define BHND_PCIE_TLP_RDDMAUPPER 0x01C /* Read DMA Upper Address */ #define BHND_PCIE_TLP_RDDMALOWER 0x020 /* Read DMA Lower Address */ #define BHND_PCIE_TLP_RDDMALENREG 0x024 /* Read DMA Len Req */ #define BHND_PCIE_TLP_MSIDMAUPPER 0x028 /* MSI DMA Upper Address */ #define BHND_PCIE_TLP_MSIDMALOWER 0x02C /* MSI DMA Lower Address */ #define BHND_PCIE_TLP_MSIDMALENREG 0x030 /* MSI DMA Len Req */ #define BHND_PCIE_TLP_SLVREQLENREG 0x034 /* Slave Request Len */ #define BHND_PCIE_TLP_FCINPUTSREQ 0x038 /* Flow Control Inputs */ #define BHND_PCIE_TLP_TXSMGRSREQ 0x03C /* Tx StateMachine and Gated Req */ #define BHND_PCIE_TLP_ADRACKCNTARBLEN 0x040 /* Address Ack XferCnt and ARB Len */ #define BHND_PCIE_TLP_DMACPLHDR0 0x044 /* DMA Completion Hdr 0 */ #define BHND_PCIE_TLP_DMACPLHDR1 0x048 /* DMA Completion Hdr 1 */ #define BHND_PCIE_TLP_DMACPLHDR2 0x04C /* DMA Completion Hdr 2 */ #define BHND_PCIE_TLP_DMACPLMISC0 0x050 /* DMA Completion Misc0 */ #define BHND_PCIE_TLP_DMACPLMISC1 0x054 /* DMA Completion Misc1 */ #define BHND_PCIE_TLP_DMACPLMISC2 0x058 /* DMA Completion Misc2 */ #define BHND_PCIE_TLP_SPTCTRLLEN 0x05C /* Split Controller Req len */ #define BHND_PCIE_TLP_SPTCTRLMSIC0 0x060 /* Split Controller Misc 0 */ #define BHND_PCIE_TLP_SPTCTRLMSIC1 0x064 /* Split Controller Misc 1 */ #define BHND_PCIE_TLP_BUSDEVFUNC 0x068 /* Bus/Device/Func */ #define BHND_PCIE_TLP_RESETCTR 0x06C /* Reset Counter */ #define BHND_PCIE_TLP_RTRYBUF 0x070 /* Retry Buffer value */ #define BHND_PCIE_TLP_TGTDEBUG1 0x074 /* Target Debug Reg1 */ #define BHND_PCIE_TLP_TGTDEBUG2 0x078 /* Target Debug Reg2 */ #define BHND_PCIE_TLP_TGTDEBUG3 0x07C /* Target Debug Reg3 */ #define BHND_PCIE_TLP_TGTDEBUG4 0x080 /* Target Debug Reg4 */ /* * PCIe-G1 SerDes MDIO Registers (>= rev10) */ #define BHND_PCIE_PHYADDR_SD 0x0 /* serdes PHY address */ #define BHND_PCIE_SD_ADDREXT 0x1F /* serdes address extension register */ #define BHND_PCIE_SD_REGS_IEEE0 0x0000 /* IEEE0 AN CTRL block */ #define BHND_PCIE_SD_REGS_IEEE1 0x0010 /* IEEE1 AN ADV block */ #define BHND_PCIE_SD_REGS_BLK0 0x8000 /* ??? */ #define BHND_PCIE_SD_REGS_BLK1 0x8010 /* ??? */ #define BHND_PCIE_SD_REGS_BLK2 0x8020 /* ??? */ #define BHND_PCIE_SD_REGS_BLK3 0x8030 /* ??? */ #define BHND_PCIE_SD_REGS_BLK4 0x8040 /* ??? */ #define BHND_PCIE_SD_REGS_PLL 0x8080 /* (?) PLL register block */ #define BHND_PCIE_SD_REGS_TX0 0x8200 /* (?) Transmit 0 block */ #define BHND_PCIE_SD_REGS_SERDESID 0x8310 /* ??? */ #define BHND_PCIE_SD_REGS_RX0 0x8400 /* (?) Receive 0 register block */ /* The interpretation of these registers and values are just guesses based on * the limited available documentation from other (likely similar) Broadcom * SerDes IP. */ #define BHND_PCIE_SD_TX_DRIVER 0x17 /* TX transmit driver register */ #define BHND_PCIE_SD_TX_DRIVER_IFIR_MASK 0x000E /* unconfirmed */ #define BHND_PCIE_SD_TX_DRIVER_IFIR_SHIFT 1 /* unconfirmed */ #define BHND_PCIE_SD_TX_DRIVER_IPRE_MASK 0x00F0 /* unconfirmed */ #define BHND_PCIE_SD_TX_DRIVER_IPRE_SHIFT 4 /* unconfirmed */ #define BHND_PCIE_SD_TX_DRIVER_IDRIVER_MASK 0x0F00 /* unconfirmed */ #define BHND_PCIE_SD_TX_DRIVER_IDRIVER_SHIFT 8 /* unconfirmed */ #define BHND_PCIE_SD_TX_DRIVER_P2_COEFF_SHIFT 12 /* unconfirmed */ #define BHND_PCIE_SD_TX_DRIVER_P2_COEFF_MASK 0xF000 /* unconfirmed */ /* Constants used with host bridge quirk handling */ #define BHND_PCIE_APPLE_TX_P2_COEFF_MAX 0x7 /* 9.6dB pre-emphassis coeff (???) */ #define BHND_PCIE_APPLE_TX_IDRIVER_MAX 0xF /* 1400mV voltage range (???) */ #define BHND_PCIE_APPLE_TX_P2_COEFF_700MV 0x7 /* 2.3dB pre-emphassis coeff (???) */ #define BHND_PCIE_APPLE_TX_IDRIVER_700MV 0x0 /* 670mV voltage range (???) */ /* * PCIe-G1 SerDes-R9 MDIO Registers (<= rev9) * * These register definitions appear to match those provided in the * "PCI Express SerDes Registers" section of the BCM5761 Ethernet Controller * Programmer's Reference Guide. */ #define BHND_PCIE_PHY_SDR9_PLL 0x1C /* SerDes PLL PHY Address*/ #define BHND_PCIE_SDR9_PLL_CTRL 0x17 /* PLL control reg */ #define BHND_PCIE_SDR9_PLL_CTRL_FREQDET_EN 0x4000 /* bit 14 is FREQDET on */ #define BHND_PCIE_PHY_SDR9_TXRX 0x0F /* SerDes RX/TX PHY Address */ #define BHND_PCIE_SDR9_RX_CTRL 0x11 /* RX ctrl register */ #define BHND_PCIE_SDR9_RX_CTRL_FORCE 0x80 /* rxpolarity_force */ #define BHND_PCIE_SDR9_RX_CTRL_POLARITY_INV 0x40 /* rxpolarity_value (if set, inverse polarity) */ #define BHND_PCIE_SDR9_RX_CDR 0x16 /* RX CDR ctrl register */ #define BHND_PCIE_SDR9_RX_CDR_FREQ_OVR_EN 0x0100 /* freq_override_en flag */ #define BHND_PCIE_SDR9_RX_CDR_FREQ_OVR_MASK 0x00FF /* freq_override_val */ #define BHND_PCIE_SDR9_RX_CDR_FREQ_OVR_SHIFT 0 #define BHND_PCIE_SDR9_RX_CDRBW 0x17 /* RX CDR bandwidth (PLL tuning) */ #define BHND_PCIE_SDR9_RX_CDRBW_INTGTRK_MASK 0x7000 /* integral loop bandwidth (phase tracking mode) */ #define BHND_PCIE_SDR9_RX_CDRBW_INTGTRK_SHIFT 11 #define BHND_PCIE_SDR9_RX_CDRBW_INTGACQ_MASK 0x0700 /* integral loop bandwidth (phase acquisition mode) */ #define BHND_PCIE_SDR9_RX_CDRBW_INTGACQ_SHIFT 8 #define BHND_PCIE_SDR9_RX_CDRBW_PROPTRK_MASK 0x0070 /* proportional loop bandwidth (phase tracking mode) */ #define BHND_PCIE_SDR9_RX_CDRBW_PROPTRK_SHIFT 4 #define BHND_PCIE_SDR9_RX_CDRBW_PROPACQ_MASK 0x0007 /* proportional loop bandwidth (phase acquisition mode) */ #define BHND_PCIE_SDR9_RX_CDRBW_PROPACQ_SHIFT 0 #define BHND_PCIE_SDR9_RX_TIMER1 0x12 /* timer1 register */ #define BHND_PCIE_SDR9_RX_TIMER1_LKTRK_MASK 0xFF00 /* phase tracking delay before asserting RX seq completion (in 16ns units) */ #define BHND_PCIE_SDR9_RX_TIMER1_LKTRK_SHIFT 8 #define BHND_PCIE_SDR9_RX_TIMER1_LKACQ_MASK 0x00FF /* phase acquisition mode time (in 1024ns units) */ #define BHND_PCIE_SDR9_RX_TIMER1_LKACQ_SHIFT 0 /* SPROM offsets */ #define BHND_PCIE_SRSH_PI_OFFSET BHND_PCI_SRSH_PI_OFFSET /**< PCI core index in SROM shadow area */ #define BHND_PCIE_SRSH_PI_MASK BHND_PCI_SRSH_PI_MASK #define BHND_PCIE_SRSH_PI_SHIFT BHND_PCI_SRSH_PI_SHIFT #define BHND_PCIE_SRSH_ASPM_OFFSET 8 /* word 4 */ #define BHND_PCIE_SRSH_ASPM_ENB 0x18 /* bit 3, 4 */ #define BHND_PCIE_SRSH_ASPM_L1_ENB 0x10 /* bit 4 */ #define BHND_PCIE_SRSH_ASPM_L0s_ENB 0x8 /* bit 3 */ #define BHND_PCIE_SRSH_PCIE_MISC_CONFIG 10 /* word 5 */ #define BHND_PCIE_SRSH_L23READY_EXIT_NOPRST 0x8000 /* bit 15 */ #define BHND_PCIE_SRSH_CLKREQ_OFFSET_R5 40 /* word 20 for srom rev <= 5 */ #define BHND_PCIE_SRSH_CLKREQ_OFFSET_R8 104 /* word 52 for srom rev 8 */ #define BHND_PCIE_SRSH_CLKREQ_ENB 0x0800 /* bit 11 */ #define BHND_PCIE_SRSH_BD_OFFSET 12 /* word 6 */ #define BHND_PCIE_SRSH_AUTOINIT_OFFSET 36 /* auto initialization enable */ /* Status reg PCIE_PLP_STATUSREG */ #define BHND_PCIE_PLP_POLARITY_INV 0x10 /* lane polarity is inverted */ #endif /* _BHND_CORES_PCI_BHND_PCIREG_H_ */ Index: head/sys/dev/bhnd/cores/pcie2/bhnd_pcie2_reg.h =================================================================== --- head/sys/dev/bhnd/cores/pcie2/bhnd_pcie2_reg.h (revision 326079) +++ head/sys/dev/bhnd/cores/pcie2/bhnd_pcie2_reg.h (revision 326080) @@ -1,228 +1,239 @@ /*- * Copyright (c) 2016 Landon Fuller * Copyright (c) 2015 Broadcom Corporation * All rights reserved. * * This file is derived from the pcie_core.h and pcie2_core.h headers * from Broadcom's Linux driver sources as distributed by dd-wrt. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * $FreeBSD$ */ #ifndef _BHND_CORES_PCIE2_BHND_PCIE2_REG_H_ #define _BHND_CORES_PCIE2_BHND_PCIE2_REG_H_ +/* + * PCIe-Gen2 DMA Constants + */ + +#define BHND_PCIE2_DMA64_TRANSLATION 0x8000000000000000 /**< PCIe-Gen2 DMA64 address translation */ +#define BHND_PCIE2_DMA64_MASK 0xc000000000000000 /**< PCIe-Gen2 DMA64 translation mask */ + +/* + * PCIe-Gen2 Core Registers + */ + #define BHND_PCIE2_CLK_CONTROL 0x000 #define BHND_PCIE2_RC_PM_CONTROL 0x004 #define BHND_PCIE2_RC_PM_STATUS 0x008 #define BHND_PCIE2_EP_PM_CONTROL 0x00C #define BHND_PCIE2_EP_PM_STATUS 0x010 #define BHND_PCIE2_EP_LTR_CONTROL 0x014 #define BHND_PCIE2_EP_LTR_STATUS 0x018 #define BHND_PCIE2_EP_OBFF_STATUS 0x01C #define BHND_PCIE2_PCIE_ERR_STATUS 0x020 #define BHND_PCIE2_RC_AXI_CONFIG 0x100 #define BHND_PCIE2_EP_AXI_CONFIG 0x104 #define BHND_PCIE2_RXDEBUG_STATUS0 0x108 #define BHND_PCIE2_RXDEBUG_CONTROL0 0x10C #define BHND_PCIE2_CONFIGINDADDR 0x120 #define BHND_PCIE2_CONFIGINDDATA 0x124 #define BHND_PCIE2_CFG_ADDR 0x1F8 #define BHND_PCIE2_CFG_DATA 0x1FC #define BHND_PCIE2_SYS_EQ_PAGE 0x200 #define BHND_PCIE2_SYS_MSI_PAGE 0x204 #define BHND_PCIE2_SYS_MSI_INTREN 0x208 #define BHND_PCIE2_SYS_MSI_CTRL0 0x210 #define BHND_PCIE2_SYS_MSI_CTRL1 0x214 #define BHND_PCIE2_SYS_MSI_CTRL2 0x218 #define BHND_PCIE2_SYS_MSI_CTRL3 0x21C #define BHND_PCIE2_SYS_MSI_CTRL4 0x220 #define BHND_PCIE2_SYS_MSI_CTRL5 0x224 #define BHND_PCIE2_SYS_EQ_HEAD0 0x250 #define BHND_PCIE2_SYS_EQ_TAIL0 0x254 #define BHND_PCIE2_SYS_EQ_HEAD1 0x258 #define BHND_PCIE2_SYS_EQ_TAIL1 0x25C #define BHND_PCIE2_SYS_EQ_HEAD2 0x260 #define BHND_PCIE2_SYS_EQ_TAIL2 0x264 #define BHND_PCIE2_SYS_EQ_HEAD3 0x268 #define BHND_PCIE2_SYS_EQ_TAIL3 0x26C #define BHND_PCIE2_SYS_EQ_HEAD4 0x270 #define BHND_PCIE2_SYS_EQ_TAIL4 0x274 #define BHND_PCIE2_SYS_EQ_HEAD5 0x278 #define BHND_PCIE2_SYS_EQ_TAIL5 0x27C #define BHND_PCIE2_SYS_RC_INTX_EN 0x330 #define BHND_PCIE2_SYS_RC_INTX_CSR 0x334 #define BHND_PCIE2_SYS_MSI_REQ 0x340 #define BHND_PCIE2_SYS_HOST_INTR_EN 0x344 #define BHND_PCIE2_SYS_HOST_INTR_CSR 0x348 #define BHND_PCIE2_SYS_HOST_INTR0 0x350 #define BHND_PCIE2_SYS_HOST_INTR1 0x354 #define BHND_PCIE2_SYS_HOST_INTR2 0x358 #define BHND_PCIE2_SYS_HOST_INTR3 0x35C #define BHND_PCIE2_SYS_EP_INT_EN0 0x360 #define BHND_PCIE2_SYS_EP_INT_EN1 0x364 #define BHND_PCIE2_SYS_EP_INT_CSR0 0x370 #define BHND_PCIE2_SYS_EP_INT_CSR1 0x374 #define BHND_PCIE2_MDIO_CTL 0x128 /**< mdio control */ #define BHND_PCIE2_MDIO_WRDATA 0x12C /**< mdio data write */ #define BHND_PCIE2_MDIO_RDDATA 0x130 /**< mdio data read */ /* DMA doorbell registers (>= rev5) */ #define BHND_PCIE2_DB0_HOST2DEV0 0x140 #define BHND_PCIE2_DB0_HOST2DEV1 0x144 #define BHND_PCIE2_DB0_DEV2HOST0 0x148 #define BHND_PCIE2_DB0_DEV2HOST1 0x14C #define BHND_PCIE2_DB1_HOST2DEV0 0x150 #define BHND_PCIE2_DB1_HOST2DEV1 0x154 #define BHND_PCIE2_DB1_DEV2HOST0 0x158 #define BHND_PCIE2_DB1_DEV2HOST1 0x15C #define BHND_PCIE2_DB2_HOST2DEV0 0x160 #define BHND_PCIE2_DB2_HOST2DEV1 0x164 #define BHND_PCIE2_DB2_DEV2HOST0 0x168 #define BHND_PCIE2_DB2_DEV2HOST1 0x16C #define BHND_PCIE2_DB3_HOST2DEV0 0x170 #define BHND_PCIE2_DB3_HOST2DEV1 0x174 #define BHND_PCIE2_DB3_DEV2HOST0 0x178 #define BHND_PCIE2_DB3_DEV2HOST1 0x17C #define BHND_PCIE2_DATAINTF 0x180 #define BHND_PCIE2_INTRLAZY0_DEV2HOST 0x188 #define BHND_PCIE2_INTRLAZY0_HOST2DEV 0x18c #define BHND_PCIE2_INTSTAT0_HOST2DEV 0x190 #define BHND_PCIE2_INTMASK0_HOST2DEV 0x194 #define BHND_PCIE2_INTSTAT0_DEV2HOST 0x198 #define BHND_PCIE2_INTMASK0_DEV2HOST 0x19c #define BHND_PCIE2_LTR_STATE 0x1A0 #define BHND_PCIE2_PWR_INT_STATUS 0x1A4 #define BHND_PCIE2_PWR_INT_MASK 0x1A8 /* DMA channel registers */ #define BHND_PCIE2_DMA0_HOST2DEV_TX 0x200 #define BHND_PCIE2_DMA0_HOST2DEV_RX 0x220 #define BHND_PCIE2_DMA0_DEV2HOST_TX 0x240 #define BHND_PCIE2_DMA0_DEV2HOST_RX 0x260 #define BHND_PCIE2_DMA1_HOST2DEV_TX 0x280 #define BHND_PCIE2_DMA1_HOST2DEV_RX 0x2A0 #define BHND_PCIE2_DMA1_DEV2HOST_TX 0x2C0 #define BHND_PCIE2_DMA1_DEV2HOST_RX 0x2E0 #define BHND_PCIE2_DMA2_HOST2DEV_TX 0x300 #define BHND_PCIE2_DMA2_HOST2DEV_RX 0x320 #define BHND_PCIE2_DMA2_DEV2HOST_TX 0x340 #define BHND_PCIE2_DMA2_DEV2HOST_RX 0x360 #define BHND_PCIE2_DMA3_HOST2DEV_TX 0x380 #define BHND_PCIE2_DMA3_HOST2DEV_RX 0x3A0 #define BHND_PCIE2_DMA3_DEV2HOST_TX 0x3C0 #define BHND_PCIE2_DMA3_DEV2HOST_RX 0x3E0 #define BHND_PCIE2_PCIE_FUNC0_CFG 0x400 /**< PCIe function 0 config space */ #define BHND_PCIE2_PCIE_FUNC1_CFG 0x500 /**< PCIe function 1 config space */ #define BHND_PCIE2_PCIE_FUNC2_CFG 0x600 /**< PCIe function 2 config space */ #define BHND_PCIE2_PCIE_FUNC3_CFG 0x700 /**< PCIe function 3 config space */ #define BHND_PCIE2_SPROM 0x800 /**< SPROM shadow */ #define BHND_PCIE2_FUNC0_IMAP0_0 0xC00 #define BHND_PCIE2_FUNC0_IMAP0_1 0xC04 #define BHND_PCIE2_FUNC0_IMAP0_2 0xC08 #define BHND_PCIE2_FUNC0_IMAP0_3 0xC0C #define BHND_PCIE2_FUNC0_IMAP0_4 0xC10 #define BHND_PCIE2_FUNC0_IMAP0_5 0xC14 #define BHND_PCIE2_FUNC0_IMAP0_6 0xC18 #define BHND_PCIE2_FUNC0_IMAP0_7 0xC1C #define BHND_PCIE2_FUNC1_IMAP0_0 0xC20 #define BHND_PCIE2_FUNC1_IMAP0_1 0xC24 #define BHND_PCIE2_FUNC1_IMAP0_2 0xC28 #define BHND_PCIE2_FUNC1_IMAP0_3 0xC2C #define BHND_PCIE2_FUNC1_IMAP0_4 0xC30 #define BHND_PCIE2_FUNC1_IMAP0_5 0xC34 #define BHND_PCIE2_FUNC1_IMAP0_6 0xC38 #define BHND_PCIE2_FUNC1_IMAP0_7 0xC3C #define BHND_PCIE2_FUNC0_IMAP1 0xC80 #define BHND_PCIE2_FUNC1_IMAP1 0xC88 #define BHND_PCIE2_FUNC0_IMAP2 0xCC0 #define BHND_PCIE2_FUNC1_IMAP2 0xCC8 #define BHND_PCIE2_IARR0_LOWER 0xD00 #define BHND_PCIE2_IARR0_UPPER 0xD04 #define BHND_PCIE2_IARR1_LOWER 0xD08 #define BHND_PCIE2_IARR1_UPPER 0xD0C #define BHND_PCIE2_IARR2_LOWER 0xD10 #define BHND_PCIE2_IARR2_UPPER 0xD14 #define BHND_PCIE2_OARR0 0xD20 #define BHND_PCIE2_OARR1 0xD28 #define BHND_PCIE2_OARR2 0xD30 #define BHND_PCIE2_OMAP0_LOWER 0xD40 #define BHND_PCIE2_OMAP0_UPPER 0xD44 #define BHND_PCIE2_OMAP1_LOWER 0xD48 #define BHND_PCIE2_OMAP1_UPPER 0xD4C #define BHND_PCIE2_OMAP2_LOWER 0xD50 #define BHND_PCIE2_OMAP2_UPPER 0xD54 #define BHND_PCIE2_FUNC1_IARR1_SIZE 0xD58 #define BHND_PCIE2_FUNC1_IARR2_SIZE 0xD5C #define BHND_PCIE2_MEM_CONTROL 0xF00 #define BHND_PCIE2_MEM_ECC_ERRLOG0 0xF04 #define BHND_PCIE2_MEM_ECC_ERRLOG1 0xF08 #define BHND_PCIE2_LINK_STATUS 0xF0C #define BHND_PCIE2_STRAP_STATUS 0xF10 #define BHND_PCIE2_RESET_STATUS 0xF14 #define BHND_PCIE2_RESETEN_IN_LINKDOWN 0xF18 #define BHND_PCIE2_MISC_INTR_EN 0xF1C #define BHND_PCIE2_TX_DEBUG_CFG 0xF20 #define BHND_PCIE2_MISC_CONFIG 0xF24 #define BHND_PCIE2_MISC_STATUS 0xF28 #define BHND_PCIE2_INTR_EN 0xF30 #define BHND_PCIE2_INTR_CLEAR 0xF34 #define BHND_PCIE2_INTR_STATUS 0xF38 /* BHND_PCIE2_MDIO_CTL */ #define BHND_PCIE2_MDIOCTL_DIVISOR_MASK 0x7f /* clock to be used on MDIO */ #define BHND_PCIE2_MDIOCTL_DIVISOR_VAL 0x2 #define BHND_PCIE2_MDIOCTL_REGADDR_SHIFT 8 /* Regaddr shift */ #define BHND_PCIE2_MDIOCTL_REGADDR_MASK 0x00FFFF00 /* Regaddr Mask */ #define BHND_PCIE2_MDIOCTL_DEVADDR_SHIFT 24 /* Physmedia devaddr shift */ #define BHND_PCIE2_MDIOCTL_DEVADDR_MASK 0x0f000000 /* Physmedia devaddr Mask */ #define BHND_PCIE2_MDIOCTL_SLAVE_BYPASS 0x10000000 /* IP slave bypass */ #define BHND_PCIE2_MDIOCTL_READ 0x20000000 /* IP slave bypass */ /* BHND_PCIE2_MDIO_DATA */ #define BHND_PCIE2_MDIODATA_DONE 0x80000000 /* rd/wr transaction done */ #define BHND_PCIE2_MDIODATA_MASK 0x7FFFFFFF /* rd/wr transaction data */ #define BHND_PCIE2_MDIODATA_DEVADDR_SHIFT 4 /* Physmedia devaddr shift */ /* BHND_PCIE2_DMA[0-4]_HOST2DEV_(TX|RX) per-channel register offsets */ #define BHND_PCIE2_DMA_CTRL 0x0 /**< enable, et al */ #define BHND_PCIE2_DMA_PTR 0x4 /**< last descriptor posted to chip */ #define BHND_PCIE2_DMA_ADDRL 0x8 /**< descriptor ring base address low 32-bits (8K aligned) */ #define BHND_PCIE2_DMA_ADDRH 0xC /**< descriptor ring base address bits 63:32 (8K aligned) */ #define BHND_PCIE2_DMA_STATUS0 0x10 /**< current descriptor, xmt state */ #define BHND_PCIE2_DMA_STATUS1 0x10 /**< active descriptor, xmt error */ #endif /* _BHND_CORES_PCIE2_BHND_PCIE2_REG_H_ */ Index: head/sys/mips/broadcom/bhnd_nexus.c =================================================================== --- head/sys/mips/broadcom/bhnd_nexus.c (revision 326079) +++ head/sys/mips/broadcom/bhnd_nexus.c (revision 326080) @@ -1,217 +1,258 @@ /*- * Copyright (c) 2015-2016 Landon Fuller * 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: * 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$ */ #include __FBSDID("$FreeBSD$"); /* * bhnd(4) driver mix-in providing shared common methods for * bhnd bus devices attached via a MIPS root nexus. */ #include #include #include #include #include #include #include #include #include #include #include +#include + #include "bcm_machdep.h" #include "bcm_mipsvar.h" #include "bhnd_nexusvar.h" /** * Default bhnd_nexus implementation of BHND_BUS_GET_SERVICE_REGISTRY(). */ static struct bhnd_service_registry * bhnd_nexus_get_service_registry(device_t dev, device_t child) { struct bcm_platform *bp = bcm_get_platform(); return (&bp->services); } /** * Default bhnd_nexus implementation of BHND_BUS_ACTIVATE_RESOURCE(). */ static int bhnd_nexus_activate_resource(device_t dev, device_t child, int type, int rid, struct bhnd_resource *r) { int error; /* Always direct */ if ((error = bus_activate_resource(child, type, rid, r->res))) return (error); r->direct = true; return (0); } /** * Default bhnd_nexus implementation of BHND_BUS_DEACTIVATE_RESOURCE(). */ static int bhnd_nexus_deactivate_resource(device_t dev, device_t child, int type, int rid, struct bhnd_resource *r) { int error; /* Always direct */ KASSERT(r->direct, ("indirect resource delegated to bhnd_nexus\n")); if ((error = bus_deactivate_resource(child, type, rid, r->res))) return (error); r->direct = false; return (0); } /** * Default bhnd_nexus implementation of BHND_BUS_IS_HW_DISABLED(). */ static bool bhnd_nexus_is_hw_disabled(device_t dev, device_t child) { struct bcm_platform *bp; struct bhnd_chipid *cid; bp = bcm_get_platform(); cid = &bp->cid; /* The BCM4706 low-cost package leaves secondary GMAC cores * floating */ if (cid->chip_id == BHND_CHIPID_BCM4706 && cid->chip_pkg == BHND_PKGID_BCM4706L && bhnd_get_device(child) == BHND_COREID_4706_GMAC && bhnd_get_core_unit(child) != 0) { return (true); } return (false); } /** * Default bhnd_nexus implementation of BHND_BUS_AGET_ATTACH_TYPE(). */ static bhnd_attach_type bhnd_nexus_get_attach_type(device_t dev, device_t child) { return (BHND_ATTACH_NATIVE); } /** * Default bhnd_nexus implementation of BHND_BUS_GET_CHIPID(). */ static const struct bhnd_chipid * bhnd_nexus_get_chipid(device_t dev, device_t child) { return (&bcm_get_platform()->cid); } /** * Default bhnd_nexus implementation of BHND_BUS_MAP_INTR(). */ static int bhnd_nexus_map_intr(device_t dev, device_t child, u_int intr, rman_res_t *irq) { 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_UNMAP_INTR(). */ static void bhnd_nexus_unmap_intr(device_t dev, device_t child, rman_res_t irq) { if (irq > UINT_MAX) panic("invalid irq: %ju", (uintmax_t)irq); intr_unmap_irq(irq); } +/** + * Default bhnd_nexus implementation of BHND_BUS_GET_DMA_TRANSLATION(). + */ +static int +bhnd_nexus_get_dma_translation(device_t dev, device_t child, + u_int width, uint32_t flags, bus_dma_tag_t *dmat, + struct bhnd_dma_translation *translation) +{ + struct bcm_platform *bp = bcm_get_platform(); + + /* We don't (currently) support any flags */ + if (flags != 0x0) + return (ENOENT); + + KASSERT(width > 0 && width <= BHND_DMA_ADDR_64BIT, + ("invalid width %u", width)); + + if (width > BHND_DMA_ADDR_32BIT) { + /* Backplane must support 64-bit addressing */ + if (!(bp->cc_caps & CHIPC_CAP_BKPLN64)) + return (ENOENT); + } + + /* No DMA address translation required */ + if (dmat != NULL) + *dmat = bus_get_dma_tag(dev); + + if (translation != NULL) { + *translation = (struct bhnd_dma_translation) { + .base_addr = 0x0, + .addr_mask = BHND_DMA_ADDR_BITMASK(width), + .addrext_mask = 0 + }; + } + + return (0); +} + static device_method_t bhnd_nexus_methods[] = { /* bhnd interface */ DEVMETHOD(bhnd_bus_get_service_registry,bhnd_nexus_get_service_registry), DEVMETHOD(bhnd_bus_register_provider, bhnd_bus_generic_sr_register_provider), DEVMETHOD(bhnd_bus_deregister_provider, bhnd_bus_generic_sr_deregister_provider), DEVMETHOD(bhnd_bus_retain_provider, bhnd_bus_generic_sr_retain_provider), DEVMETHOD(bhnd_bus_release_provider, bhnd_bus_generic_sr_release_provider), DEVMETHOD(bhnd_bus_activate_resource, bhnd_nexus_activate_resource), DEVMETHOD(bhnd_bus_deactivate_resource, bhnd_nexus_deactivate_resource), 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_dma_translation, bhnd_nexus_get_dma_translation), 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 }; DEFINE_CLASS_0(bhnd, bhnd_nexus_driver, bhnd_nexus_methods, sizeof(struct bhnd_softc));