610 lines
16 KiB
C
610 lines
16 KiB
C
/*
|
|
* Copyright (c) 2006-2022, RT-Thread Development Team
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Change Logs:
|
|
* Date Author Notes
|
|
* 2022-10-24 GuEe-GUI first version
|
|
*/
|
|
|
|
#include <rthw.h>
|
|
#include <rtthread.h>
|
|
|
|
#define DBG_TAG "pci.ofw"
|
|
#define DBG_LVL DBG_INFO
|
|
#include <rtdbg.h>
|
|
|
|
#include <drivers/pci.h>
|
|
#include <drivers/ofw.h>
|
|
#include <drivers/ofw_io.h>
|
|
#include <drivers/ofw_irq.h>
|
|
#include <drivers/ofw_fdt.h>
|
|
|
|
static rt_err_t pci_ofw_irq_parse(struct rt_pci_device *pdev, struct rt_ofw_cell_args *out_irq)
|
|
{
|
|
rt_err_t err = RT_EOK;
|
|
rt_uint8_t pin;
|
|
fdt32_t map_addr[4];
|
|
struct rt_pci_device *p2pdev;
|
|
struct rt_ofw_node *dev_np, *p2pnode = RT_NULL;
|
|
|
|
/* Parse device tree if dev have a device node */
|
|
dev_np = pdev->parent.ofw_node;
|
|
|
|
if (dev_np)
|
|
{
|
|
err = rt_ofw_parse_irq_cells(dev_np, 0, out_irq);
|
|
|
|
if (err)
|
|
{
|
|
return err;
|
|
}
|
|
}
|
|
|
|
/* Assume #interrupt-cells is 1 */
|
|
if ((err = rt_pci_read_config_u8(pdev, PCIR_INTPIN, &pin)))
|
|
{
|
|
goto _err;
|
|
}
|
|
|
|
/* No pin, exit with no error message. */
|
|
if (pin == 0)
|
|
{
|
|
return -RT_ENOSYS;
|
|
}
|
|
|
|
/* Try local interrupt-map in the device node */
|
|
if (rt_ofw_prop_read_raw(dev_np, "interrupt-map", RT_NULL))
|
|
{
|
|
pin = rt_pci_irq_intx(pdev, pin);
|
|
p2pnode = dev_np;
|
|
}
|
|
|
|
/* Walk up the PCI tree */
|
|
while (!p2pnode)
|
|
{
|
|
p2pdev = pdev->bus->self;
|
|
|
|
/* Is the root bus -> host bridge */
|
|
if (rt_pci_is_root_bus(pdev->bus))
|
|
{
|
|
struct rt_pci_host_bridge *host_bridge = pdev->bus->host_bridge;
|
|
|
|
p2pnode = host_bridge->parent.ofw_node;
|
|
|
|
if (!p2pnode)
|
|
{
|
|
err = -RT_EINVAL;
|
|
|
|
goto _err;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Is P2P bridge */
|
|
p2pnode = p2pdev->parent.ofw_node;
|
|
}
|
|
|
|
if (p2pnode)
|
|
{
|
|
break;
|
|
}
|
|
|
|
/* Try get INTx in P2P */
|
|
pin = rt_pci_irq_intx(pdev, pin);
|
|
pdev = p2pdev;
|
|
}
|
|
|
|
/* For more format detail, please read `components/drivers/ofw/irq.c:ofw_parse_irq_map` */
|
|
out_irq->data = map_addr;
|
|
out_irq->args_count = 2;
|
|
out_irq->args[0] = 3;
|
|
out_irq->args[1] = 1;
|
|
|
|
/* In addr cells */
|
|
map_addr[0] = cpu_to_fdt32((pdev->bus->number << 16) | (pdev->devfn << 8));
|
|
map_addr[1] = cpu_to_fdt32(0);
|
|
map_addr[2] = cpu_to_fdt32(0);
|
|
/* In pin cells */
|
|
map_addr[3] = cpu_to_fdt32(pin);
|
|
|
|
err = rt_ofw_parse_irq_map(p2pnode, out_irq);
|
|
|
|
_err:
|
|
if (err == -RT_EEMPTY)
|
|
{
|
|
LOG_W("PCI-Device<%s> no interrupt-map found, INTx interrupts not available",
|
|
rt_dm_dev_get_name(&pdev->parent));
|
|
LOG_W("PCI-Device<%s> possibly some PCI slots don't have level triggered interrupts capability",
|
|
rt_dm_dev_get_name(&pdev->parent));
|
|
}
|
|
else if (err && err != -RT_ENOSYS)
|
|
{
|
|
LOG_E("PCI-Device<%s> irq parse failed with err = %s",
|
|
rt_dm_dev_get_name(&pdev->parent), rt_strerror(err));
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
int rt_pci_ofw_irq_parse_and_map(struct rt_pci_device *pdev,
|
|
rt_uint8_t slot, rt_uint8_t pin)
|
|
{
|
|
int irq = -1;
|
|
rt_err_t status;
|
|
struct rt_ofw_cell_args irq_args;
|
|
|
|
if (!pdev)
|
|
{
|
|
goto _end;
|
|
}
|
|
|
|
status = pci_ofw_irq_parse(pdev, &irq_args);
|
|
|
|
if (status)
|
|
{
|
|
goto _end;
|
|
}
|
|
|
|
irq = rt_ofw_map_irq(&irq_args);
|
|
|
|
if (irq >= 0)
|
|
{
|
|
pdev->intx_pic = rt_pic_dynamic_cast(rt_ofw_data(irq_args.data));
|
|
}
|
|
|
|
_end:
|
|
return irq;
|
|
}
|
|
|
|
static rt_err_t pci_ofw_parse_ranges(struct rt_ofw_node *dev_np, const char *propname,
|
|
int phy_addr_cells, int phy_size_cells, int cpu_addr_cells,
|
|
struct rt_pci_bus_region **out_regions, rt_size_t *out_regions_nr)
|
|
{
|
|
const fdt32_t *cell;
|
|
rt_ssize_t total_cells;
|
|
int groups, space_code;
|
|
rt_uint32_t phy_addr[3];
|
|
rt_uint64_t cpu_addr, phy_addr_size;
|
|
|
|
*out_regions = RT_NULL;
|
|
*out_regions_nr = 0;
|
|
cell = rt_ofw_prop_read_raw(dev_np, propname, &total_cells);
|
|
|
|
if (!cell)
|
|
{
|
|
return -RT_EEMPTY;
|
|
}
|
|
|
|
groups = total_cells / sizeof(*cell) / (phy_addr_cells + phy_size_cells + cpu_addr_cells);
|
|
*out_regions = rt_malloc(groups * sizeof(struct rt_pci_bus_region));
|
|
|
|
if (!*out_regions)
|
|
{
|
|
return -RT_ENOMEM;
|
|
}
|
|
|
|
for (int i = 0; i < groups; ++i)
|
|
{
|
|
/*
|
|
* ranges:
|
|
* phys.hi cell: npt000ss bbbbbbbb dddddfff rrrrrrrr
|
|
* phys.low cell: llllllll llllllll llllllll llllllll
|
|
* phys.mid cell: hhhhhhhh hhhhhhhh hhhhhhhh hhhhhhhh
|
|
*
|
|
* n: relocatable region flag (doesn't play a role here)
|
|
* p: prefetchable (cacheable) region flag
|
|
* t: aliased address flag (doesn't play a role here)
|
|
* ss: space code
|
|
* 00: configuration space
|
|
* 01: I/O space
|
|
* 10: 32 bit memory space
|
|
* 11: 64 bit memory space
|
|
* bbbbbbbb: The PCI bus number
|
|
* ddddd: The device number
|
|
* fff: The function number. Used for multifunction PCI devices.
|
|
* rrrrrrrr: Register number; used for configuration cycles.
|
|
*/
|
|
|
|
for (int j = 0; j < phy_addr_cells; ++j)
|
|
{
|
|
phy_addr[j] = rt_fdt_read_number(cell++, 1);
|
|
}
|
|
|
|
space_code = (phy_addr[0] >> 24) & 0x3;
|
|
|
|
cpu_addr = rt_fdt_read_number(cell, cpu_addr_cells);
|
|
cell += cpu_addr_cells;
|
|
phy_addr_size = rt_fdt_read_number(cell, phy_size_cells);
|
|
cell += phy_size_cells;
|
|
|
|
(*out_regions)[i].phy_addr = ((rt_uint64_t)phy_addr[1] << 32) | phy_addr[2];
|
|
(*out_regions)[i].cpu_addr = cpu_addr;
|
|
(*out_regions)[i].size = phy_addr_size;
|
|
|
|
(*out_regions)[i].bus_start = (*out_regions)[i].phy_addr;
|
|
|
|
if (space_code & 2)
|
|
{
|
|
(*out_regions)[i].flags = phy_addr[0] & (1U << 30) ?
|
|
PCI_BUS_REGION_F_PREFETCH : PCI_BUS_REGION_F_MEM;
|
|
}
|
|
else if (space_code & 1)
|
|
{
|
|
(*out_regions)[i].flags = PCI_BUS_REGION_F_IO;
|
|
}
|
|
else
|
|
{
|
|
(*out_regions)[i].flags = PCI_BUS_REGION_F_NONE;
|
|
}
|
|
|
|
++*out_regions_nr;
|
|
}
|
|
|
|
return RT_EOK;
|
|
}
|
|
|
|
rt_err_t rt_pci_ofw_parse_ranges(struct rt_ofw_node *dev_np,
|
|
struct rt_pci_host_bridge *host_bridge)
|
|
{
|
|
rt_err_t err;
|
|
int phy_addr_cells = -1, phy_size_cells = -1, cpu_addr_cells;
|
|
|
|
if (!dev_np || !host_bridge)
|
|
{
|
|
return -RT_EINVAL;
|
|
}
|
|
|
|
cpu_addr_cells = rt_ofw_io_addr_cells(dev_np);
|
|
rt_ofw_prop_read_s32(dev_np, "#address-cells", &phy_addr_cells);
|
|
rt_ofw_prop_read_s32(dev_np, "#size-cells", &phy_size_cells);
|
|
|
|
if (phy_addr_cells != 3 || phy_size_cells < 1 || cpu_addr_cells < 1)
|
|
{
|
|
return -RT_EINVAL;
|
|
}
|
|
|
|
if (pci_ofw_parse_ranges(dev_np, "ranges",
|
|
phy_addr_cells, phy_size_cells, cpu_addr_cells,
|
|
&host_bridge->bus_regions, &host_bridge->bus_regions_nr))
|
|
{
|
|
return -RT_EINVAL;
|
|
}
|
|
|
|
if ((err = rt_pci_region_setup(host_bridge)))
|
|
{
|
|
rt_free(host_bridge->bus_regions);
|
|
host_bridge->bus_regions_nr = 0;
|
|
|
|
return err;
|
|
}
|
|
|
|
err = pci_ofw_parse_ranges(dev_np, "dma-ranges",
|
|
phy_addr_cells, phy_size_cells, cpu_addr_cells,
|
|
&host_bridge->dma_regions, &host_bridge->dma_regions_nr);
|
|
|
|
if (err != -RT_EEMPTY)
|
|
{
|
|
rt_free(host_bridge->bus_regions);
|
|
host_bridge->bus_regions_nr = 0;
|
|
|
|
LOG_E("%s: Read dma-ranges error = %s", rt_ofw_node_full_name(dev_np),
|
|
rt_strerror(err));
|
|
|
|
return err;
|
|
}
|
|
|
|
return RT_EOK;
|
|
}
|
|
|
|
rt_err_t rt_pci_ofw_host_bridge_init(struct rt_ofw_node *dev_np,
|
|
struct rt_pci_host_bridge *host_bridge)
|
|
{
|
|
rt_err_t err;
|
|
const char *propname;
|
|
|
|
if (!dev_np || !host_bridge)
|
|
{
|
|
return -RT_EINVAL;
|
|
}
|
|
|
|
host_bridge->irq_slot = rt_pci_irq_slot;
|
|
host_bridge->irq_map = rt_pci_ofw_irq_parse_and_map;
|
|
|
|
if (rt_ofw_prop_read_u32_array_index(dev_np, "bus-range", 0, 2, host_bridge->bus_range) < 0)
|
|
{
|
|
return -RT_EIO;
|
|
}
|
|
|
|
propname = rt_ofw_get_prop_fuzzy_name(dev_np, ",pci-domain$");
|
|
rt_ofw_prop_read_u32(dev_np, propname, &host_bridge->domain);
|
|
|
|
err = rt_pci_ofw_parse_ranges(dev_np, host_bridge);
|
|
|
|
return err;
|
|
}
|
|
|
|
rt_err_t rt_pci_ofw_bus_init(struct rt_pci_bus *bus)
|
|
{
|
|
rt_err_t err = RT_EOK;
|
|
|
|
return err;
|
|
}
|
|
|
|
rt_err_t rt_pci_ofw_bus_free(struct rt_pci_bus *bus)
|
|
{
|
|
rt_err_t err = RT_EOK;
|
|
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* RID (Requester ID) is formatted such that:
|
|
* Bits [15:8] are the Bus number.
|
|
* Bits [7:3] are the Device number.
|
|
* Bits [2:0] are the Function number.
|
|
*
|
|
* msi-map: Maps a Requester ID to an MSI controller and associated
|
|
* msi-specifier data. The property is an arbitrary number of tuples of
|
|
* (rid-base,msi-controller,msi-base,length), where:
|
|
*
|
|
* - rid-base is a single cell describing the first RID matched by the entry.
|
|
*
|
|
* - msi-controller is a single phandle to an MSI controller
|
|
*
|
|
* - msi-base is an msi-specifier describing the msi-specifier produced for
|
|
* the first RID matched by the entry.
|
|
*
|
|
* - length is a single cell describing how many consecutive RIDs are matched
|
|
* following the rid-base.
|
|
*
|
|
* Any RID r in the interval [rid-base, rid-base + length) is associated with
|
|
* the listed msi-controller, with the msi-specifier (r - rid-base + msi-base).
|
|
*
|
|
* msi-map-mask: A mask to be applied to each Requester ID prior to being mapped
|
|
* to an msi-specifier per the msi-map property.
|
|
*
|
|
* msi-parent: Describes the MSI parent of the root complex itself. Where
|
|
* the root complex and MSI controller do not pass sideband data with MSI
|
|
* writes, this property may be used to describe the MSI controller(s)
|
|
* used by PCI devices under the root complex, if defined as such in the
|
|
* binding for the root complex.
|
|
*
|
|
* / {
|
|
* #address-cells = <1>;
|
|
* #size-cells = <1>;
|
|
*
|
|
* msi_a: msi-controller@a {
|
|
* reg = <0xa 0x1>;
|
|
* msi-controller;
|
|
* #msi-cells = <1>;
|
|
* };
|
|
*
|
|
* msi_b: msi-controller@b {
|
|
* reg = <0xb 0x1>;
|
|
* msi-controller;
|
|
* #msi-cells = <1>;
|
|
* };
|
|
*
|
|
* msi_c: msi-controller@c {
|
|
* reg = <0xc 0x1>;
|
|
* msi-controller;
|
|
* #msi-cells = <1>;
|
|
* };
|
|
*
|
|
* Example (1)
|
|
* ===========
|
|
* pci: pci@f {
|
|
* reg = <0xf 0x1>;
|
|
* device_type = "pci";
|
|
*
|
|
* // The sideband data provided to the MSI controller is
|
|
* // the RID, identity-mapped.
|
|
* msi-map = <0x0 &msi_a 0x0 0x10000>;
|
|
* };
|
|
*
|
|
* Example (2)
|
|
* ===========
|
|
* pci: pci@ff {
|
|
* reg = <0xff 0x1>;
|
|
* device_type = "pci";
|
|
*
|
|
* // The sideband data provided to the MSI controller is
|
|
* // the RID, masked to only the device and function bits.
|
|
* msi-map = <0x0 &msi_a 0x0 0x100>;
|
|
* msi-map-mask = <0xff>
|
|
* };
|
|
*
|
|
* Example (3)
|
|
* ===========
|
|
* pci: pci@fff {
|
|
* reg = <0xfff 0x1>;
|
|
* device_type = "pci";
|
|
*
|
|
* // The sideband data provided to the MSI controller is
|
|
* // the RID, but the high bit of the bus number is ignored.
|
|
* msi-map = <0x0000 &msi_a 0x0000 0x8000>,
|
|
* <0x8000 &msi_a 0x0000 0x8000>;
|
|
* };
|
|
*
|
|
* Example (4)
|
|
* ===========
|
|
* pci: pci@f {
|
|
* reg = <0xf 0x1>;
|
|
* device_type = "pci";
|
|
*
|
|
* // The sideband data provided to the MSI controller is
|
|
* // the RID, but the high bit of the bus number is negated.
|
|
* msi-map = <0x0000 &msi 0x8000 0x8000>,
|
|
* <0x8000 &msi 0x0000 0x8000>;
|
|
* };
|
|
*
|
|
* Example (5)
|
|
* ===========
|
|
* pci: pci@f {
|
|
* reg = <0xf 0x1>;
|
|
* device_type = "pci";
|
|
*
|
|
* // The sideband data provided to MSI controller a is the
|
|
* // RID, but the high bit of the bus number is negated.
|
|
* // The sideband data provided to MSI controller b is the
|
|
* // RID, identity-mapped.
|
|
* // MSI controller c is not addressable.
|
|
* msi-map = <0x0000 &msi_a 0x8000 0x08000>,
|
|
* <0x8000 &msi_a 0x0000 0x08000>,
|
|
* <0x0000 &msi_b 0x0000 0x10000>;
|
|
* };
|
|
* };
|
|
*/
|
|
static void ofw_msi_pic_init(struct rt_pci_device *pdev)
|
|
{
|
|
#ifdef RT_PCI_MSI
|
|
rt_uint32_t rid;
|
|
struct rt_pci_host_bridge *bridge;
|
|
struct rt_ofw_node *np, *msi_ic_np = RT_NULL;
|
|
|
|
/*
|
|
* NOTE: Typically, a device's RID is equal to the PCI device's ID.
|
|
* However, in complex bus management scenarios such as servers and PCs,
|
|
* the RID needs to be associated with DMA. In these cases,
|
|
* the RID should be equal to the DMA alias assigned to the
|
|
* PCI device by the system bus.
|
|
*/
|
|
rid = rt_pci_dev_id(pdev);
|
|
|
|
bridge = rt_pci_find_host_bridge(pdev->bus);
|
|
RT_ASSERT(bridge != RT_NULL);
|
|
|
|
np = bridge->parent.ofw_node;
|
|
|
|
if (!(msi_ic_np = rt_ofw_parse_phandle(np, "msi-parent", 0)))
|
|
{
|
|
rt_ofw_map_id(np, rid, "msi-map", "msi-map-mask", &msi_ic_np, RT_NULL);
|
|
}
|
|
|
|
if (!msi_ic_np)
|
|
{
|
|
LOG_W("%s: MSI PIC not found", rt_dm_dev_get_name(&pdev->parent));
|
|
|
|
return;
|
|
}
|
|
|
|
pdev->msi_pic = rt_pic_dynamic_cast(rt_ofw_data(msi_ic_np));
|
|
|
|
if (!pdev->msi_pic)
|
|
{
|
|
LOG_W("%s: '%s' not supported", rt_dm_dev_get_name(&pdev->parent), "msi-parent");
|
|
|
|
goto _out_put_msi_parent_node;
|
|
}
|
|
|
|
if (!pdev->msi_pic->ops->irq_compose_msi_msg)
|
|
{
|
|
LOG_E("%s: MSI pic MUST implemented %s",
|
|
rt_ofw_node_full_name(msi_ic_np), "irq_compose_msi_msg");
|
|
RT_ASSERT(0);
|
|
}
|
|
|
|
if (!pdev->msi_pic->ops->irq_alloc_msi)
|
|
{
|
|
LOG_E("%s: MSI pic MUST implemented %s",
|
|
rt_ofw_node_full_name(msi_ic_np), "irq_alloc_msi");
|
|
RT_ASSERT(0);
|
|
}
|
|
|
|
if (!pdev->msi_pic->ops->irq_free_msi)
|
|
{
|
|
LOG_E("%s: MSI pic MUST implemented %s",
|
|
rt_ofw_node_full_name(msi_ic_np), "irq_free_msi");
|
|
RT_ASSERT(0);
|
|
}
|
|
|
|
_out_put_msi_parent_node:
|
|
rt_ofw_node_put(msi_ic_np);
|
|
#endif
|
|
}
|
|
|
|
static rt_int32_t ofw_pci_devfn(struct rt_ofw_node *np)
|
|
{
|
|
rt_int32_t res;
|
|
rt_uint32_t reg[5];
|
|
|
|
res = rt_ofw_prop_read_u32_array_index(np, "reg", 0, RT_ARRAY_SIZE(reg), reg);
|
|
|
|
return res > 0 ? ((reg[0] >> 8) & 0xff) : res;
|
|
}
|
|
|
|
static struct rt_ofw_node *ofw_find_device(struct rt_ofw_node *np, rt_uint32_t devfn)
|
|
{
|
|
struct rt_ofw_node *dev_np, *mfd_np;
|
|
|
|
rt_ofw_foreach_child_node(np, dev_np)
|
|
{
|
|
if (ofw_pci_devfn(dev_np) == devfn)
|
|
{
|
|
return dev_np;
|
|
}
|
|
|
|
if (rt_ofw_node_tag_equ(dev_np, "multifunc-device"))
|
|
{
|
|
rt_ofw_foreach_child_node(dev_np, mfd_np)
|
|
{
|
|
if (ofw_pci_devfn(mfd_np) == devfn)
|
|
{
|
|
rt_ofw_node_put(dev_np);
|
|
|
|
return mfd_np;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return RT_NULL;
|
|
}
|
|
|
|
rt_err_t rt_pci_ofw_device_init(struct rt_pci_device *pdev)
|
|
{
|
|
struct rt_ofw_node *np = RT_NULL;
|
|
|
|
if (!pdev)
|
|
{
|
|
return -RT_EINVAL;
|
|
}
|
|
|
|
ofw_msi_pic_init(pdev);
|
|
|
|
if (rt_pci_is_root_bus(pdev->bus) || !pdev->bus->self)
|
|
{
|
|
struct rt_pci_host_bridge *host_bridge;
|
|
|
|
host_bridge = rt_pci_find_host_bridge(pdev->bus);
|
|
RT_ASSERT(host_bridge != RT_NULL);
|
|
|
|
np = host_bridge->parent.ofw_node;
|
|
}
|
|
else
|
|
{
|
|
np = pdev->bus->self->parent.ofw_node;
|
|
}
|
|
|
|
if (np)
|
|
{
|
|
pdev->parent.ofw_node = ofw_find_device(np, pdev->devfn);
|
|
}
|
|
|
|
return RT_EOK;
|
|
}
|
|
|
|
rt_err_t rt_pci_ofw_device_free(struct rt_pci_device *pdev)
|
|
{
|
|
if (!pdev)
|
|
{
|
|
return -RT_EINVAL;
|
|
}
|
|
|
|
rt_ofw_node_put(pdev->parent.ofw_node);
|
|
|
|
return RT_EOK;
|
|
}
|