b0484678d4
When compiling current on arm64, it breaks since LA_UNLOCKED is undefined. This was in turn caused by the mutex code aliasing it as MA_NOTOWNED. Add <sys/lock.h> so the macro is defined.
975 lines
23 KiB
C
975 lines
23 KiB
C
/*-
|
|
* 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>
|
|
#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
|
|
#ifdef FDT
|
|
phandle_t msi_parent;
|
|
#endif
|
|
int error;
|
|
|
|
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));
|