Commit 65af618d authored by Zhichang Yuan's avatar Zhichang Yuan Committed by Bjorn Helgaas

of: Add missing I/O range exception for indirect-IO devices

There are some special ISA/LPC devices that work on a specific I/O range
where it is not correct to specify a 'ranges' property in the DTS parent
node as CPU addresses translated from DTS node are only for memory space on
some architectures, such as ARM64.  Without the parent 'ranges' property,
of_translate_address() returns an error.

Here we add special handling for this case.

During the OF address translation, some checking will be performed to
identify whether the device node is registered as indirect-IO.  If it is,
the I/O translation will be done in a different way from that one of PCI
MMIO.  In this way, the I/O 'reg' property of the special ISA/LPC devices
will be parsed correctly.
Tested-by: default avatardann frazier <dann.frazier@canonical.com>
Signed-off-by: default avatarZhichang Yuan <yuanzhichang@hisilicon.com>
Signed-off-by: default avatarGabriele Paoloni <gabriele.paoloni@huawei.com>
Signed-off-by: Arnd Bergmann <arnd@arndb.de>    # earlier draft
Signed-off-by: default avatarBjorn Helgaas <bhelgaas@google.com>
Reviewed-by: default avatarAndy Shevchenko <andy.shevchenko@gmail.com>
Acked-by: default avatarRob Herring <robh@kernel.org>
parent 5745392e
......@@ -5,6 +5,7 @@
#include <linux/fwnode.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/logic_pio.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/pci.h>
......@@ -562,9 +563,14 @@ static int of_translate_one(struct device_node *parent, struct of_bus *bus,
* that translation is impossible (that is we are not dealing with a value
* that can be mapped to a cpu physical address). This is not really specified
* that way, but this is traditionally the way IBM at least do things
*
* Whenever the translation fails, the *host pointer will be set to the
* device that had registered logical PIO mapping, and the return code is
* relative to that node.
*/
static u64 __of_translate_address(struct device_node *dev,
const __be32 *in_addr, const char *rprop)
const __be32 *in_addr, const char *rprop,
struct device_node **host)
{
struct device_node *parent = NULL;
struct of_bus *bus, *pbus;
......@@ -577,6 +583,7 @@ static u64 __of_translate_address(struct device_node *dev,
/* Increase refcount at current level */
of_node_get(dev);
*host = NULL;
/* Get parent & match bus type */
parent = of_get_parent(dev);
if (parent == NULL)
......@@ -597,6 +604,8 @@ static u64 __of_translate_address(struct device_node *dev,
/* Translate */
for (;;) {
struct logic_pio_hwaddr *iorange;
/* Switch to parent bus */
of_node_put(dev);
dev = parent;
......@@ -609,6 +618,19 @@ static u64 __of_translate_address(struct device_node *dev,
break;
}
/*
* For indirectIO device which has no ranges property, get
* the address from reg directly.
*/
iorange = find_io_range_by_fwnode(&dev->fwnode);
if (iorange && (iorange->flags != LOGIC_PIO_CPU_MMIO)) {
result = of_read_number(addr + 1, na - 1);
pr_debug("indirectIO matched(%pOF) 0x%llx\n",
dev, result);
*host = of_node_get(dev);
break;
}
/* Get new parent bus and counts */
pbus = of_match_bus(parent);
pbus->count_cells(dev, &pna, &pns);
......@@ -640,13 +662,32 @@ static u64 __of_translate_address(struct device_node *dev,
u64 of_translate_address(struct device_node *dev, const __be32 *in_addr)
{
return __of_translate_address(dev, in_addr, "ranges");
struct device_node *host;
u64 ret;
ret = __of_translate_address(dev, in_addr, "ranges", &host);
if (host) {
of_node_put(host);
return OF_BAD_ADDR;
}
return ret;
}
EXPORT_SYMBOL(of_translate_address);
u64 of_translate_dma_address(struct device_node *dev, const __be32 *in_addr)
{
return __of_translate_address(dev, in_addr, "dma-ranges");
struct device_node *host;
u64 ret;
ret = __of_translate_address(dev, in_addr, "dma-ranges", &host);
if (host) {
of_node_put(host);
return OF_BAD_ADDR;
}
return ret;
}
EXPORT_SYMBOL(of_translate_dma_address);
......@@ -688,29 +729,48 @@ const __be32 *of_get_address(struct device_node *dev, int index, u64 *size,
}
EXPORT_SYMBOL(of_get_address);
static u64 of_translate_ioport(struct device_node *dev, const __be32 *in_addr,
u64 size)
{
u64 taddr;
unsigned long port;
struct device_node *host;
taddr = __of_translate_address(dev, in_addr, "ranges", &host);
if (host) {
/* host-specific port access */
port = logic_pio_trans_hwaddr(&host->fwnode, taddr, size);
of_node_put(host);
} else {
/* memory-mapped I/O range */
port = pci_address_to_pio(taddr);
}
if (port == (unsigned long)-1)
return OF_BAD_ADDR;
return port;
}
static int __of_address_to_resource(struct device_node *dev,
const __be32 *addrp, u64 size, unsigned int flags,
const char *name, struct resource *r)
{
u64 taddr;
if ((flags & (IORESOURCE_IO | IORESOURCE_MEM)) == 0)
if (flags & IORESOURCE_MEM)
taddr = of_translate_address(dev, addrp);
else if (flags & IORESOURCE_IO)
taddr = of_translate_ioport(dev, addrp, size);
else
return -EINVAL;
taddr = of_translate_address(dev, addrp);
if (taddr == OF_BAD_ADDR)
return -EINVAL;
memset(r, 0, sizeof(struct resource));
if (flags & IORESOURCE_IO) {
unsigned long port;
port = pci_address_to_pio(taddr);
if (port == (unsigned long)-1)
return -EINVAL;
r->start = port;
r->end = port + size - 1;
} else {
r->start = taddr;
r->end = taddr + size - 1;
}
r->start = taddr;
r->end = taddr + size - 1;
r->flags = flags;
r->name = name ? name : dev->full_name;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment