opnsense-src/sys/dev/dpaa2/dpaa2_mc.c

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

975 lines
23 KiB
C
Raw Normal View History

Add initial DPAA2 support DPAA2 is a hardware-level networking architecture found in some NXP SoCs which contain hardware blocks including Management Complex (MC, a command interface to manipulate DPAA2 objects), Wire Rate I/O processor (WRIOP, packets distribution, queuing, drop decisions), Queues and Buffers Manager (QBMan, Rx/Tx queues control, Rx buffer pools) and the others. The Management Complex runs NXP-supplied firmware which provides DPAA2 objects as an abstraction layer over those blocks to simplify an access to the underlying hardware. Each DPAA2 object has its own driver (to perform an initialization at least) and will be visible as a separate device in the device tree. Two new drivers (dpaa2_mc and dpaa2_rc) act like firmware buses in order to form a hierarchy of the DPAA2 devices: acpiX (or simplebusX) dpaa2_mcX dpaa2_rcX dpaa2_mcp0 ... dpaa2_mcpN dpaa2_bpX dpaa2_macX dpaa2_io0 ... dpaa2_ioM dpaa2_niX dpaa2_mc is suppossed to be a root of the hierarchy, comes in ACPI and FDT flavours and implements helper interfaces to allocate and assign bus resources, MSI and "managed" DPAA2 devices (NXP treats some of the objects as resources for the other DPAA2 objects to let them function properly). Almost all of the DPAA2 objects are assigned to the resource containers (dpaa2_rc) to implement isolation. The initial implementation focuses on the DPAA2 network interface to be operational. It is the most complex object in terms of dependencies which uses I/O objects to transmit/receive packets. Approved by: bz (mentor) Tested by: manu, bz MFC after: 3 months Differential Revision: https://reviews.freebsd.org/D36638
2022-09-20 05:47:41 -04:00
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright © 2021-2022 Dmitry Salychev
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
/*
* The DPAA2 Management Complex (MC) bus driver.
*
* MC is a hardware resource manager which can be found in several NXP
* SoCs (LX2160A, for example) and provides an access to the specialized
* hardware objects used in network-oriented packet processing applications.
*/
#include "opt_acpi.h"
#include "opt_platform.h"
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/bus.h>
#include <sys/rman.h>
#include <sys/lock.h>
Add initial DPAA2 support DPAA2 is a hardware-level networking architecture found in some NXP SoCs which contain hardware blocks including Management Complex (MC, a command interface to manipulate DPAA2 objects), Wire Rate I/O processor (WRIOP, packets distribution, queuing, drop decisions), Queues and Buffers Manager (QBMan, Rx/Tx queues control, Rx buffer pools) and the others. The Management Complex runs NXP-supplied firmware which provides DPAA2 objects as an abstraction layer over those blocks to simplify an access to the underlying hardware. Each DPAA2 object has its own driver (to perform an initialization at least) and will be visible as a separate device in the device tree. Two new drivers (dpaa2_mc and dpaa2_rc) act like firmware buses in order to form a hierarchy of the DPAA2 devices: acpiX (or simplebusX) dpaa2_mcX dpaa2_rcX dpaa2_mcp0 ... dpaa2_mcpN dpaa2_bpX dpaa2_macX dpaa2_io0 ... dpaa2_ioM dpaa2_niX dpaa2_mc is suppossed to be a root of the hierarchy, comes in ACPI and FDT flavours and implements helper interfaces to allocate and assign bus resources, MSI and "managed" DPAA2 devices (NXP treats some of the objects as resources for the other DPAA2 objects to let them function properly). Almost all of the DPAA2 objects are assigned to the resource containers (dpaa2_rc) to implement isolation. The initial implementation focuses on the DPAA2 network interface to be operational. It is the most complex object in terms of dependencies which uses I/O objects to transmit/receive packets. Approved by: bz (mentor) Tested by: manu, bz MFC after: 3 months Differential Revision: https://reviews.freebsd.org/D36638
2022-09-20 05:47:41 -04:00
#include <sys/module.h>
#include <sys/malloc.h>
#include <sys/mutex.h>
#include <sys/queue.h>
#include <vm/vm.h>
#include <machine/bus.h>
#include <machine/resource.h>
#ifdef DEV_ACPI
#include <contrib/dev/acpica/include/acpi.h>
#include <dev/acpica/acpivar.h>
#endif
#ifdef FDT
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#include <dev/ofw/ofw_pci.h>
#endif
#include "pcib_if.h"
#include "pci_if.h"
#include "dpaa2_mc.h"
/* Macros to read/write MC registers */
#define mcreg_read_4(_sc, _r) bus_read_4(&(_sc)->map[1], (_r))
#define mcreg_write_4(_sc, _r, _v) bus_write_4(&(_sc)->map[1], (_r), (_v))
#define COMPARE_TYPE(t, v) (strncmp((v), (t), strlen((v))) == 0)
#define IORT_DEVICE_NAME "MCE"
/* MC Registers */
#define MC_REG_GCR1 0x0000u
#define MC_REG_GCR2 0x0004u /* TODO: Does it exist? */
#define MC_REG_GSR 0x0008u
#define MC_REG_FAPR 0x0028u
/* General Control Register 1 (GCR1) */
#define GCR1_P1_STOP 0x80000000u
#define GCR1_P2_STOP 0x40000000u
/* General Status Register (GSR) */
#define GSR_HW_ERR(v) (((v) & 0x80000000u) >> 31)
#define GSR_CAT_ERR(v) (((v) & 0x40000000u) >> 30)
#define GSR_DPL_OFFSET(v) (((v) & 0x3FFFFF00u) >> 8)
#define GSR_MCS(v) (((v) & 0xFFu) >> 0)
/* Timeouts to wait for the MC status. */
#define MC_STAT_TIMEOUT 1000u /* us */
#define MC_STAT_ATTEMPTS 100u
/**
* @brief Structure to describe a DPAA2 device as a managed resource.
*/
struct dpaa2_mc_devinfo {
STAILQ_ENTRY(dpaa2_mc_devinfo) link;
device_t dpaa2_dev;
uint32_t flags;
uint32_t owners;
};
MALLOC_DEFINE(M_DPAA2_MC, "dpaa2_mc", "DPAA2 Management Complex");
static struct resource_spec dpaa2_mc_spec[] = {
{ SYS_RES_MEMORY, 0, RF_ACTIVE | RF_UNMAPPED },
{ SYS_RES_MEMORY, 1, RF_ACTIVE | RF_UNMAPPED | RF_OPTIONAL },
RESOURCE_SPEC_END
};
static u_int dpaa2_mc_get_xref(device_t, device_t);
static u_int dpaa2_mc_map_id(device_t, device_t, uintptr_t *);
static struct rman *dpaa2_mc_rman(device_t, int);
static int dpaa2_mc_alloc_msi_impl(device_t, device_t, int, int, int *);
static int dpaa2_mc_release_msi_impl(device_t, device_t, int, int *);
static int dpaa2_mc_map_msi_impl(device_t, device_t, int, uint64_t *,
uint32_t *);
/*
* For device interface.
*/
int
dpaa2_mc_attach(device_t dev)
{
struct dpaa2_mc_softc *sc;
struct resource_map_request req;
uint32_t val;
int error;
sc = device_get_softc(dev);
sc->dev = dev;
sc->msi_allocated = false;
sc->msi_owner = NULL;
error = bus_alloc_resources(sc->dev, dpaa2_mc_spec, sc->res);
if (error) {
device_printf(dev, "%s: failed to allocate resources\n",
__func__);
return (ENXIO);
}
if (sc->res[1]) {
resource_init_map_request(&req);
req.memattr = VM_MEMATTR_DEVICE;
error = bus_map_resource(sc->dev, SYS_RES_MEMORY, sc->res[1],
&req, &sc->map[1]);
if (error) {
device_printf(dev, "%s: failed to map control "
"registers\n", __func__);
dpaa2_mc_detach(dev);
return (ENXIO);
}
if (bootverbose)
device_printf(dev,
"GCR1=0x%x, GCR2=0x%x, GSR=0x%x, FAPR=0x%x\n",
mcreg_read_4(sc, MC_REG_GCR1),
mcreg_read_4(sc, MC_REG_GCR2),
mcreg_read_4(sc, MC_REG_GSR),
mcreg_read_4(sc, MC_REG_FAPR));
/* Reset P1_STOP and P2_STOP bits to resume MC processor. */
val = mcreg_read_4(sc, MC_REG_GCR1) &
~(GCR1_P1_STOP | GCR1_P2_STOP);
mcreg_write_4(sc, MC_REG_GCR1, val);
/* Poll MC status. */
if (bootverbose)
device_printf(dev, "polling MC status...\n");
for (int i = 0; i < MC_STAT_ATTEMPTS; i++) {
val = mcreg_read_4(sc, MC_REG_GSR);
if (GSR_MCS(val) != 0u)
break;
DELAY(MC_STAT_TIMEOUT);
}
if (bootverbose)
device_printf(dev,
"GCR1=0x%x, GCR2=0x%x, GSR=0x%x, FAPR=0x%x\n",
mcreg_read_4(sc, MC_REG_GCR1),
mcreg_read_4(sc, MC_REG_GCR2),
mcreg_read_4(sc, MC_REG_GSR),
mcreg_read_4(sc, MC_REG_FAPR));
}
/* At least 64 bytes of the command portal should be available. */
if (rman_get_size(sc->res[0]) < DPAA2_MCP_MEM_WIDTH) {
device_printf(dev, "%s: MC portal memory region too small: "
"%jd\n", __func__, rman_get_size(sc->res[0]));
dpaa2_mc_detach(dev);
return (ENXIO);
}
/* Map MC portal memory resource. */
resource_init_map_request(&req);
req.memattr = VM_MEMATTR_DEVICE;
error = bus_map_resource(sc->dev, SYS_RES_MEMORY, sc->res[0],
&req, &sc->map[0]);
if (error) {
device_printf(dev, "Failed to map MC portal memory\n");
dpaa2_mc_detach(dev);
return (ENXIO);
}
/* Initialize a resource manager for the DPAA2 I/O objects. */
sc->dpio_rman.rm_type = RMAN_ARRAY;
sc->dpio_rman.rm_descr = "DPAA2 DPIO objects";
error = rman_init(&sc->dpio_rman);
if (error) {
device_printf(dev, "Failed to initialize a resource manager for "
"the DPAA2 I/O objects: error=%d\n", error);
dpaa2_mc_detach(dev);
return (ENXIO);
}
/* Initialize a resource manager for the DPAA2 buffer pools. */
sc->dpbp_rman.rm_type = RMAN_ARRAY;
sc->dpbp_rman.rm_descr = "DPAA2 DPBP objects";
error = rman_init(&sc->dpbp_rman);
if (error) {
device_printf(dev, "Failed to initialize a resource manager for "
"the DPAA2 buffer pools: error=%d\n", error);
dpaa2_mc_detach(dev);
return (ENXIO);
}
/* Initialize a resource manager for the DPAA2 concentrators. */
sc->dpcon_rman.rm_type = RMAN_ARRAY;
sc->dpcon_rman.rm_descr = "DPAA2 DPCON objects";
error = rman_init(&sc->dpcon_rman);
if (error) {
device_printf(dev, "Failed to initialize a resource manager for "
"the DPAA2 concentrators: error=%d\n", error);
dpaa2_mc_detach(dev);
return (ENXIO);
}
/* Initialize a resource manager for the DPAA2 MC portals. */
sc->dpmcp_rman.rm_type = RMAN_ARRAY;
sc->dpmcp_rman.rm_descr = "DPAA2 DPMCP objects";
error = rman_init(&sc->dpmcp_rman);
if (error) {
device_printf(dev, "Failed to initialize a resource manager for "
"the DPAA2 MC portals: error=%d\n", error);
dpaa2_mc_detach(dev);
return (ENXIO);
}
/* Initialize a list of non-allocatable DPAA2 devices. */
mtx_init(&sc->mdev_lock, "MC portal mdev lock", NULL, MTX_DEF);
STAILQ_INIT(&sc->mdev_list);
mtx_init(&sc->msi_lock, "MC MSI lock", NULL, MTX_DEF);
/*
* Add a root resource container as the only child of the bus. All of
* the direct descendant containers will be attached to the root one
* instead of the MC device.
*/
sc->rcdev = device_add_child(dev, "dpaa2_rc", 0);
if (sc->rcdev == NULL) {
dpaa2_mc_detach(dev);
return (ENXIO);
}
bus_generic_probe(dev);
bus_generic_attach(dev);
return (0);
}
int
dpaa2_mc_detach(device_t dev)
{
struct dpaa2_mc_softc *sc;
struct dpaa2_devinfo *dinfo = NULL;
int error;
bus_generic_detach(dev);
sc = device_get_softc(dev);
if (sc->rcdev)
device_delete_child(dev, sc->rcdev);
bus_release_resources(dev, dpaa2_mc_spec, sc->res);
dinfo = device_get_ivars(dev);
if (dinfo)
free(dinfo, M_DPAA2_MC);
error = bus_generic_detach(dev);
if (error != 0)
return (error);
return (device_delete_children(dev));
}
/*
* For bus interface.
*/
struct resource *
dpaa2_mc_alloc_resource(device_t mcdev, device_t child, int type, int *rid,
rman_res_t start, rman_res_t end, rman_res_t count, u_int flags)
{
struct resource *res;
struct rman *rm;
int error;
rm = dpaa2_mc_rman(mcdev, type);
if (!rm)
return (BUS_ALLOC_RESOURCE(device_get_parent(mcdev), child,
type, rid, start, end, count, flags));
/*
* Skip managing DPAA2-specific resource. It must be provided to MC by
* calling DPAA2_MC_MANAGE_DEV() beforehand.
*/
if (type <= DPAA2_DEV_MC) {
error = rman_manage_region(rm, start, end);
if (error) {
device_printf(mcdev, "rman_manage_region() failed: "
"start=%#jx, end=%#jx, error=%d\n", start, end,
error);
goto fail;
}
}
res = rman_reserve_resource(rm, start, end, count, flags, child);
if (!res) {
device_printf(mcdev, "rman_reserve_resource() failed: "
"start=%#jx, end=%#jx, count=%#jx\n", start, end, count);
goto fail;
}
rman_set_rid(res, *rid);
if (flags & RF_ACTIVE) {
if (bus_activate_resource(child, type, *rid, res)) {
device_printf(mcdev, "bus_activate_resource() failed: "
"rid=%d, res=%#jx\n", *rid, (uintmax_t) res);
rman_release_resource(res);
goto fail;
}
}
return (res);
fail:
device_printf(mcdev, "%s() failed: type=%d, rid=%d, start=%#jx, "
"end=%#jx, count=%#jx, flags=%x\n", __func__, type, *rid, start, end,
count, flags);
return (NULL);
}
int
dpaa2_mc_adjust_resource(device_t mcdev, device_t child, int type,
struct resource *r, rman_res_t start, rman_res_t end)
{
struct rman *rm;
rm = dpaa2_mc_rman(mcdev, type);
if (rm)
return (rman_adjust_resource(r, start, end));
return (bus_generic_adjust_resource(mcdev, child, type, r, start, end));
}
int
dpaa2_mc_release_resource(device_t mcdev, device_t child, int type, int rid,
struct resource *r)
{
struct rman *rm;
rm = dpaa2_mc_rman(mcdev, type);
if (rm) {
KASSERT(rman_is_region_manager(r, rm), ("rman mismatch"));
rman_release_resource(r);
}
return (bus_generic_release_resource(mcdev, child, type, rid, r));
}
int
dpaa2_mc_activate_resource(device_t mcdev, device_t child, int type, int rid,
struct resource *r)
{
int rc;
if ((rc = rman_activate_resource(r)) != 0)
return (rc);
return (BUS_ACTIVATE_RESOURCE(device_get_parent(mcdev), child, type,
rid, r));
}
int
dpaa2_mc_deactivate_resource(device_t mcdev, device_t child, int type, int rid,
struct resource *r)
{
int rc;
if ((rc = rman_deactivate_resource(r)) != 0)
return (rc);
return (BUS_DEACTIVATE_RESOURCE(device_get_parent(mcdev), child, type,
rid, r));
}
/*
* For pseudo-pcib interface.
*/
int
dpaa2_mc_alloc_msi(device_t mcdev, device_t child, int count, int maxcount,
int *irqs)
{
#if defined(INTRNG)
return (dpaa2_mc_alloc_msi_impl(mcdev, child, count, maxcount, irqs));
#else
return (ENXIO);
#endif
}
int
dpaa2_mc_release_msi(device_t mcdev, device_t child, int count, int *irqs)
{
#if defined(INTRNG)
return (dpaa2_mc_release_msi_impl(mcdev, child, count, irqs));
#else
return (ENXIO);
#endif
}
int
dpaa2_mc_map_msi(device_t mcdev, device_t child, int irq, uint64_t *addr,
uint32_t *data)
{
#if defined(INTRNG)
return (dpaa2_mc_map_msi_impl(mcdev, child, irq, addr, data));
#else
return (ENXIO);
#endif
}
int
dpaa2_mc_get_id(device_t mcdev, device_t child, enum pci_id_type type,
uintptr_t *id)
{
struct dpaa2_devinfo *dinfo;
dinfo = device_get_ivars(child);
if (strcmp(device_get_name(mcdev), "dpaa2_mc") != 0)
return (ENXIO);
if (type == PCI_ID_MSI)
return (dpaa2_mc_map_id(mcdev, child, id));
*id = dinfo->icid;
return (0);
}
/*
* For DPAA2 Management Complex bus driver interface.
*/
int
dpaa2_mc_manage_dev(device_t mcdev, device_t dpaa2_dev, uint32_t flags)
{
struct dpaa2_mc_softc *sc;
struct dpaa2_devinfo *dinfo;
struct dpaa2_mc_devinfo *di;
struct rman *rm;
int error;
sc = device_get_softc(mcdev);
dinfo = device_get_ivars(dpaa2_dev);
if (!sc || !dinfo || strcmp(device_get_name(mcdev), "dpaa2_mc") != 0)
return (EINVAL);
di = malloc(sizeof(*di), M_DPAA2_MC, M_WAITOK | M_ZERO);
if (!di)
return (ENOMEM);
di->dpaa2_dev = dpaa2_dev;
di->flags = flags;
di->owners = 0;
/* Append a new managed DPAA2 device to the queue. */
mtx_assert(&sc->mdev_lock, MA_NOTOWNED);
mtx_lock(&sc->mdev_lock);
STAILQ_INSERT_TAIL(&sc->mdev_list, di, link);
mtx_unlock(&sc->mdev_lock);
if (flags & DPAA2_MC_DEV_ALLOCATABLE) {
/* Select rman based on a type of the DPAA2 device. */
rm = dpaa2_mc_rman(mcdev, dinfo->dtype);
if (!rm)
return (ENOENT);
/* Manage DPAA2 device as an allocatable resource. */
error = rman_manage_region(rm, (rman_res_t) dpaa2_dev,
(rman_res_t) dpaa2_dev);
if (error)
return (error);
}
return (0);
}
int
dpaa2_mc_get_free_dev(device_t mcdev, device_t *dpaa2_dev,
enum dpaa2_dev_type devtype)
{
struct rman *rm;
rman_res_t start, end;
int error;
if (strcmp(device_get_name(mcdev), "dpaa2_mc") != 0)
return (EINVAL);
/* Select resource manager based on a type of the DPAA2 device. */
rm = dpaa2_mc_rman(mcdev, devtype);
if (!rm)
return (ENOENT);
/* Find first free DPAA2 device of the given type. */
error = rman_first_free_region(rm, &start, &end);
if (error)
return (error);
KASSERT(start == end, ("start != end, but should be the same pointer "
"to the DPAA2 device: start=%jx, end=%jx", start, end));
*dpaa2_dev = (device_t) start;
return (0);
}
int
dpaa2_mc_get_dev(device_t mcdev, device_t *dpaa2_dev,
enum dpaa2_dev_type devtype, uint32_t obj_id)
{
struct dpaa2_mc_softc *sc;
struct dpaa2_devinfo *dinfo;
struct dpaa2_mc_devinfo *di;
int error = ENOENT;
sc = device_get_softc(mcdev);
if (!sc || strcmp(device_get_name(mcdev), "dpaa2_mc") != 0)
return (EINVAL);
mtx_assert(&sc->mdev_lock, MA_NOTOWNED);
mtx_lock(&sc->mdev_lock);
STAILQ_FOREACH(di, &sc->mdev_list, link) {
dinfo = device_get_ivars(di->dpaa2_dev);
if (dinfo->dtype == devtype && dinfo->id == obj_id) {
*dpaa2_dev = di->dpaa2_dev;
error = 0;
break;
}
}
mtx_unlock(&sc->mdev_lock);
return (error);
}
int
dpaa2_mc_get_shared_dev(device_t mcdev, device_t *dpaa2_dev,
enum dpaa2_dev_type devtype)
{
struct dpaa2_mc_softc *sc;
struct dpaa2_devinfo *dinfo;
struct dpaa2_mc_devinfo *di;
device_t dev = NULL;
uint32_t owners = UINT32_MAX;
int error = ENOENT;
sc = device_get_softc(mcdev);
if (!sc || strcmp(device_get_name(mcdev), "dpaa2_mc") != 0)
return (EINVAL);
mtx_assert(&sc->mdev_lock, MA_NOTOWNED);
mtx_lock(&sc->mdev_lock);
STAILQ_FOREACH(di, &sc->mdev_list, link) {
dinfo = device_get_ivars(di->dpaa2_dev);
if ((dinfo->dtype == devtype) &&
(di->flags & DPAA2_MC_DEV_SHAREABLE) &&
(di->owners < owners)) {
dev = di->dpaa2_dev;
owners = di->owners;
}
}
if (dev) {
*dpaa2_dev = dev;
error = 0;
}
mtx_unlock(&sc->mdev_lock);
return (error);
}
int
dpaa2_mc_reserve_dev(device_t mcdev, device_t dpaa2_dev,
enum dpaa2_dev_type devtype)
{
struct dpaa2_mc_softc *sc;
struct dpaa2_mc_devinfo *di;
int error = ENOENT;
sc = device_get_softc(mcdev);
if (!sc || strcmp(device_get_name(mcdev), "dpaa2_mc") != 0)
return (EINVAL);
mtx_assert(&sc->mdev_lock, MA_NOTOWNED);
mtx_lock(&sc->mdev_lock);
STAILQ_FOREACH(di, &sc->mdev_list, link) {
if (di->dpaa2_dev == dpaa2_dev &&
(di->flags & DPAA2_MC_DEV_SHAREABLE)) {
di->owners++;
error = 0;
break;
}
}
mtx_unlock(&sc->mdev_lock);
return (error);
}
int
dpaa2_mc_release_dev(device_t mcdev, device_t dpaa2_dev,
enum dpaa2_dev_type devtype)
{
struct dpaa2_mc_softc *sc;
struct dpaa2_mc_devinfo *di;
int error = ENOENT;
sc = device_get_softc(mcdev);
if (!sc || strcmp(device_get_name(mcdev), "dpaa2_mc") != 0)
return (EINVAL);
mtx_assert(&sc->mdev_lock, MA_NOTOWNED);
mtx_lock(&sc->mdev_lock);
STAILQ_FOREACH(di, &sc->mdev_list, link) {
if (di->dpaa2_dev == dpaa2_dev &&
(di->flags & DPAA2_MC_DEV_SHAREABLE)) {
di->owners -= di->owners > 0 ? 1 : 0;
error = 0;
break;
}
}
mtx_unlock(&sc->mdev_lock);
return (error);
}
/**
* @brief Convert DPAA2 device type to string.
*/
const char *
dpaa2_ttos(enum dpaa2_dev_type type)
{
switch (type) {
case DPAA2_DEV_MC:
return ("mc"); /* NOTE: to print as information only. */
case DPAA2_DEV_RC:
return ("dprc");
case DPAA2_DEV_IO:
return ("dpio");
case DPAA2_DEV_NI:
return ("dpni");
case DPAA2_DEV_MCP:
return ("dpmcp");
case DPAA2_DEV_BP:
return ("dpbp");
case DPAA2_DEV_CON:
return ("dpcon");
case DPAA2_DEV_MAC:
return ("dpmac");
case DPAA2_DEV_MUX:
return ("dpdmux");
case DPAA2_DEV_SW:
return ("dpsw");
default:
break;
}
return ("notype");
}
/**
* @brief Convert string to DPAA2 device type.
*/
enum dpaa2_dev_type
dpaa2_stot(const char *str)
{
if (COMPARE_TYPE(str, "dprc")) {
return (DPAA2_DEV_RC);
} else if (COMPARE_TYPE(str, "dpio")) {
return (DPAA2_DEV_IO);
} else if (COMPARE_TYPE(str, "dpni")) {
return (DPAA2_DEV_NI);
} else if (COMPARE_TYPE(str, "dpmcp")) {
return (DPAA2_DEV_MCP);
} else if (COMPARE_TYPE(str, "dpbp")) {
return (DPAA2_DEV_BP);
} else if (COMPARE_TYPE(str, "dpcon")) {
return (DPAA2_DEV_CON);
} else if (COMPARE_TYPE(str, "dpmac")) {
return (DPAA2_DEV_MAC);
} else if (COMPARE_TYPE(str, "dpdmux")) {
return (DPAA2_DEV_MUX);
} else if (COMPARE_TYPE(str, "dpsw")) {
return (DPAA2_DEV_SW);
}
return (DPAA2_DEV_NOTYPE);
}
/**
* @internal
*/
static u_int
dpaa2_mc_get_xref(device_t mcdev, device_t child)
{
struct dpaa2_mc_softc *sc = device_get_softc(mcdev);
struct dpaa2_devinfo *dinfo = device_get_ivars(child);
#ifdef DEV_ACPI
u_int xref, devid;
#endif
2022-10-18 01:38:02 -04:00
#ifdef FDT
phandle_t msi_parent;
#endif
int error;
Add initial DPAA2 support DPAA2 is a hardware-level networking architecture found in some NXP SoCs which contain hardware blocks including Management Complex (MC, a command interface to manipulate DPAA2 objects), Wire Rate I/O processor (WRIOP, packets distribution, queuing, drop decisions), Queues and Buffers Manager (QBMan, Rx/Tx queues control, Rx buffer pools) and the others. The Management Complex runs NXP-supplied firmware which provides DPAA2 objects as an abstraction layer over those blocks to simplify an access to the underlying hardware. Each DPAA2 object has its own driver (to perform an initialization at least) and will be visible as a separate device in the device tree. Two new drivers (dpaa2_mc and dpaa2_rc) act like firmware buses in order to form a hierarchy of the DPAA2 devices: acpiX (or simplebusX) dpaa2_mcX dpaa2_rcX dpaa2_mcp0 ... dpaa2_mcpN dpaa2_bpX dpaa2_macX dpaa2_io0 ... dpaa2_ioM dpaa2_niX dpaa2_mc is suppossed to be a root of the hierarchy, comes in ACPI and FDT flavours and implements helper interfaces to allocate and assign bus resources, MSI and "managed" DPAA2 devices (NXP treats some of the objects as resources for the other DPAA2 objects to let them function properly). Almost all of the DPAA2 objects are assigned to the resource containers (dpaa2_rc) to implement isolation. The initial implementation focuses on the DPAA2 network interface to be operational. It is the most complex object in terms of dependencies which uses I/O objects to transmit/receive packets. Approved by: bz (mentor) Tested by: manu, bz MFC after: 3 months Differential Revision: https://reviews.freebsd.org/D36638
2022-09-20 05:47:41 -04:00
if (sc && dinfo) {
#ifdef DEV_ACPI
if (sc->acpi_based) {
/*
* NOTE: The first named component from the IORT table
* with the given name (as a substring) will be used.
*/
error = acpi_iort_map_named_msi(IORT_DEVICE_NAME,
dinfo->icid, &xref, &devid);
if (error)
return (0);
return (xref);
}
#endif
#ifdef FDT
if (!sc->acpi_based) {
/* FDT-based driver. */
error = ofw_bus_msimap(sc->ofw_node, dinfo->icid,
&msi_parent, NULL);
if (error)
return (0);
return ((u_int) msi_parent);
}
#endif
}
return (0);
}
/**
* @internal
*/
static u_int
dpaa2_mc_map_id(device_t mcdev, device_t child, uintptr_t *id)
{
struct dpaa2_devinfo *dinfo;
#ifdef DEV_ACPI
u_int xref, devid;
int error;
#endif
dinfo = device_get_ivars(child);
if (dinfo) {
/*
* The first named components from IORT table with the given
* name (as a substring) will be used.
*/
#ifdef DEV_ACPI
error = acpi_iort_map_named_msi(IORT_DEVICE_NAME, dinfo->icid,
&xref, &devid);
if (error == 0)
*id = devid;
else
#endif
*id = dinfo->icid; /* RID not in IORT, likely FW bug */
return (0);
}
return (ENXIO);
}
/**
* @internal
* @brief Obtain a resource manager based on the given type of the resource.
*/
static struct rman *
dpaa2_mc_rman(device_t mcdev, int type)
{
struct dpaa2_mc_softc *sc;
sc = device_get_softc(mcdev);
switch (type) {
case DPAA2_DEV_IO:
return (&sc->dpio_rman);
case DPAA2_DEV_BP:
return (&sc->dpbp_rman);
case DPAA2_DEV_CON:
return (&sc->dpcon_rman);
case DPAA2_DEV_MCP:
return (&sc->dpmcp_rman);
default:
break;
}
return (NULL);
}
#if defined(INTRNG) && !defined(IOMMU)
/**
* @internal
* @brief Allocates requested number of MSIs.
*
* NOTE: This function is a part of fallback solution when IOMMU isn't available.
* Total number of IRQs is limited to 32.
*/
static int
dpaa2_mc_alloc_msi_impl(device_t mcdev, device_t child, int count, int maxcount,
int *irqs)
{
struct dpaa2_mc_softc *sc = device_get_softc(mcdev);
int msi_irqs[DPAA2_MC_MSI_COUNT];
int error;
/* Pre-allocate a bunch of MSIs for MC to be used by its children. */
if (!sc->msi_allocated) {
error = intr_alloc_msi(mcdev, child, dpaa2_mc_get_xref(mcdev,
child), DPAA2_MC_MSI_COUNT, DPAA2_MC_MSI_COUNT, msi_irqs);
if (error) {
device_printf(mcdev, "failed to pre-allocate %d MSIs: "
"error=%d\n", DPAA2_MC_MSI_COUNT, error);
return (error);
}
mtx_assert(&sc->msi_lock, MA_NOTOWNED);
mtx_lock(&sc->msi_lock);
for (int i = 0; i < DPAA2_MC_MSI_COUNT; i++) {
sc->msi[i].child = NULL;
sc->msi[i].irq = msi_irqs[i];
}
sc->msi_owner = child;
sc->msi_allocated = true;
mtx_unlock(&sc->msi_lock);
}
error = ENOENT;
/* Find the first free MSIs from the pre-allocated pool. */
mtx_assert(&sc->msi_lock, MA_NOTOWNED);
mtx_lock(&sc->msi_lock);
for (int i = 0; i < DPAA2_MC_MSI_COUNT; i++) {
if (sc->msi[i].child != NULL)
continue;
error = 0;
for (int j = 0; j < count; j++) {
if (i + j >= DPAA2_MC_MSI_COUNT) {
device_printf(mcdev, "requested %d MSIs exceed "
"limit of %d available\n", count,
DPAA2_MC_MSI_COUNT);
error = E2BIG;
break;
}
sc->msi[i + j].child = child;
irqs[j] = sc->msi[i + j].irq;
}
break;
}
mtx_unlock(&sc->msi_lock);
return (error);
}
/**
* @internal
* @brief Marks IRQs as free in the pre-allocated pool of MSIs.
*
* NOTE: This function is a part of fallback solution when IOMMU isn't available.
* Total number of IRQs is limited to 32.
* NOTE: MSIs are kept allocated in the kernel as a part of the pool.
*/
static int
dpaa2_mc_release_msi_impl(device_t mcdev, device_t child, int count, int *irqs)
{
struct dpaa2_mc_softc *sc = device_get_softc(mcdev);
mtx_assert(&sc->msi_lock, MA_NOTOWNED);
mtx_lock(&sc->msi_lock);
for (int i = 0; i < DPAA2_MC_MSI_COUNT; i++) {
if (sc->msi[i].child != child)
continue;
for (int j = 0; j < count; j++) {
if (sc->msi[i].irq == irqs[j]) {
sc->msi[i].child = NULL;
break;
}
}
}
mtx_unlock(&sc->msi_lock);
return (0);
}
/**
* @internal
* @brief Provides address to write to and data according to the given MSI from
* the pre-allocated pool.
*
* NOTE: This function is a part of fallback solution when IOMMU isn't available.
* Total number of IRQs is limited to 32.
*/
static int
dpaa2_mc_map_msi_impl(device_t mcdev, device_t child, int irq, uint64_t *addr,
uint32_t *data)
{
struct dpaa2_mc_softc *sc = device_get_softc(mcdev);
int error = EINVAL;
mtx_assert(&sc->msi_lock, MA_NOTOWNED);
mtx_lock(&sc->msi_lock);
for (int i = 0; i < DPAA2_MC_MSI_COUNT; i++) {
if (sc->msi[i].child == child && sc->msi[i].irq == irq) {
error = 0;
break;
}
}
mtx_unlock(&sc->msi_lock);
if (error)
return (error);
return (intr_map_msi(mcdev, sc->msi_owner, dpaa2_mc_get_xref(mcdev,
sc->msi_owner), irq, addr, data));
}
#endif /* defined(INTRNG) && !defined(IOMMU) */
static device_method_t dpaa2_mc_methods[] = {
DEVMETHOD_END
};
DEFINE_CLASS_0(dpaa2_mc, dpaa2_mc_driver, dpaa2_mc_methods,
sizeof(struct dpaa2_mc_softc));