[PATCH V8 1/6] LIBIO: Introduce a generic PIO mapping method

From: zhichang.yuan
Date: Thu Mar 30 2017 - 10:58:10 EST


In commit 41f8bba7f55(of/pci: Add pci_register_io_range() and
pci_pio_to_address()), a new I/O space management was supported. With that
driver, the I/O ranges configured for PCI/PCIE hosts on some architectures can
be mapped to logical PIO, converted easily between CPU address and the
corresponding logicial PIO. Based on this, PCI I/O devices can be accessed in a
memory read/write way through the unified in/out accessors.

But on some archs/platforms, there are bus hosts which access I/O peripherals
with host-local I/O port addresses rather than memory addresses after
memory-mapped.
To support those devices, a more generic I/O mapping method is introduced here.
Through this patch, both the CPU addresses and the host-local port can be
mapped into the logical PIO space with different logical/fake PIOs. After this,
all the I/O accesses to either PCI MMIO devices or host-local I/O peripherals
can be unified into the existing I/O accessors defined in asm-generic/io.h and
be redirected to the right device-specific hooks based on the input logical PIO.

Signed-off-by: zhichang.yuan <yuanzhichang@xxxxxxxxxxxxx>
Signed-off-by: Gabriele Paoloni <gabriele.paoloni@xxxxxxxxxx>
---
include/asm-generic/io.h | 50 ++++++
include/linux/logic_pio.h | 174 +++++++++++++++++++
lib/Kconfig | 26 +++
lib/Makefile | 2 +
lib/logic_pio.c | 413 ++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 665 insertions(+)
create mode 100644 include/linux/logic_pio.h
create mode 100644 lib/logic_pio.c

diff --git a/include/asm-generic/io.h b/include/asm-generic/io.h
index 7ef015e..f7fbec3 100644
--- a/include/asm-generic/io.h
+++ b/include/asm-generic/io.h
@@ -351,6 +351,8 @@ static inline void writesq(volatile void __iomem *addr, const void *buffer,
#define IO_SPACE_LIMIT 0xffff
#endif

+#include <linux/logic_pio.h>
+
/*
* {in,out}{b,w,l}() access little endian I/O. {in,out}{b,w,l}_p() can be
* implemented on hardware that needs an additional delay for I/O accesses to
@@ -358,51 +360,75 @@ static inline void writesq(volatile void __iomem *addr, const void *buffer,
*/

#ifndef inb
+#ifdef CONFIG_INDIRECT_PIO
+#define inb logic_inb
+#else
#define inb inb
static inline u8 inb(unsigned long addr)
{
return readb(PCI_IOBASE + addr);
}
+#endif /* CONFIG_INDIRECT_PIO */
#endif

#ifndef inw
+#ifdef CONFIG_INDIRECT_PIO
+#define inw logic_inw
+#else
#define inw inw
static inline u16 inw(unsigned long addr)
{
return readw(PCI_IOBASE + addr);
}
+#endif /* CONFIG_INDIRECT_PIO */
#endif

#ifndef inl
+#ifdef CONFIG_INDIRECT_PIO
+#define inl logic_inl
+#else
#define inl inl
static inline u32 inl(unsigned long addr)
{
return readl(PCI_IOBASE + addr);
}
+#endif /* CONFIG_INDIRECT_PIO */
#endif

#ifndef outb
+#ifdef CONFIG_INDIRECT_PIO
+#define outb logic_outb
+#else
#define outb outb
static inline void outb(u8 value, unsigned long addr)
{
writeb(value, PCI_IOBASE + addr);
}
+#endif /* CONFIG_INDIRECT_PIO */
#endif

#ifndef outw
+#ifdef CONFIG_INDIRECT_PIO
+#define outw logic_outw
+#else
#define outw outw
static inline void outw(u16 value, unsigned long addr)
{
writew(value, PCI_IOBASE + addr);
}
+#endif /* CONFIG_INDIRECT_PIO */
#endif

#ifndef outl
+#ifdef CONFIG_INDIRECT_PIO
+#define outl logic_outl
+#else
#define outl outl
static inline void outl(u32 value, unsigned long addr)
{
writel(value, PCI_IOBASE + addr);
}
+#endif /* CONFIG_INDIRECT_PIO */
#endif

#ifndef inb_p
@@ -459,54 +485,78 @@ static inline void outl_p(u32 value, unsigned long addr)
*/

#ifndef insb
+#ifdef CONFIG_INDIRECT_PIO
+#define insb logic_insb
+#else
#define insb insb
static inline void insb(unsigned long addr, void *buffer, unsigned int count)
{
readsb(PCI_IOBASE + addr, buffer, count);
}
+#endif /* CONFIG_INDIRECT_PIO */
#endif

#ifndef insw
+#ifdef CONFIG_INDIRECT_PIO
+#define insw logic_insw
+#else
#define insw insw
static inline void insw(unsigned long addr, void *buffer, unsigned int count)
{
readsw(PCI_IOBASE + addr, buffer, count);
}
+#endif /* CONFIG_INDIRECT_PIO */
#endif

#ifndef insl
+#ifdef CONFIG_INDIRECT_PIO
+#define insl logic_insl
+#else
#define insl insl
static inline void insl(unsigned long addr, void *buffer, unsigned int count)
{
readsl(PCI_IOBASE + addr, buffer, count);
}
+#endif /* CONFIG_INDIRECT_PIO */
#endif

#ifndef outsb
+#ifdef CONFIG_INDIRECT_PIO
+#define outsb logic_outsb
+#else
#define outsb outsb
static inline void outsb(unsigned long addr, const void *buffer,
unsigned int count)
{
writesb(PCI_IOBASE + addr, buffer, count);
}
+#endif /* CONFIG_INDIRECT_PIO */
#endif

#ifndef outsw
+#ifdef CONFIG_INDIRECT_PIO
+#define outsw logic_outsw
+#else
#define outsw outsw
static inline void outsw(unsigned long addr, const void *buffer,
unsigned int count)
{
writesw(PCI_IOBASE + addr, buffer, count);
}
+#endif /* CONFIG_INDIRECT_PIO */
#endif

#ifndef outsl
+#ifdef CONFIG_INDIRECT_PIO
+#define outsl logic_outsl
+#else
#define outsl outsl
static inline void outsl(unsigned long addr, const void *buffer,
unsigned int count)
{
writesl(PCI_IOBASE + addr, buffer, count);
}
+#endif /* CONFIG_INDIRECT_PIO */
#endif

#ifndef insb_p
diff --git a/include/linux/logic_pio.h b/include/linux/logic_pio.h
new file mode 100644
index 0000000..e9f5644
--- /dev/null
+++ b/include/linux/logic_pio.h
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2017 Hisilicon Limited, All Rights Reserved.
+ * Author: Zhichang Yuan <yuanzhichang@xxxxxxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LINUX_LIBIO_H
+#define __LINUX_LIBIO_H
+
+#ifdef __KERNEL__
+
+#include <linux/fwnode.h>
+
+/*
+ * Total IO space is 0 to IO_SPACE_LIMIT
+ *
+ * section pio
+ * |________|________________________________________|
+ *
+ * In this division, the benefits are:
+ * 1) The MMIO PIO space is consecutive, then ioport_map() still works well
+ * for MMIO;
+ * 2) The search happened in inX/outX with input PIO will have better
+ * performance for indirect_IO. For MMIO, the performance is nearly same
+ * even when CONFIG_INDIRECT_PIO is enabled;
+ *
+ * Some notes:
+ * 1) Don't increase the IO_SPACE_LIMIT to avoid modification on so many
+ * architectural files;
+ * 2) To reduce the impact on the original I/O space to a minimum, we only
+ * apply this IO space division when CONFIG_INDIRECT_PIO is enabled; And
+ * only allocate the last section to INDIRECT_PIO, all the other PIO space are
+ * for MMIO;
+ * 3) For better efficiency, one more I/O segment can be separated from 'pio'
+ * bit section. But it will make the IO space size decreased. Won't apply at
+ * this moment;
+ */
+#ifdef CONFIG_INDIRECT_PIO
+#define PIO_SECT_BITS 2
+#else
+#define PIO_SECT_BITS 0
+#endif
+#define PIO_MAX_SECT (0x01UL << PIO_SECT_BITS)
+#define PIO_SECT_MASK (PIO_MAX_SECT - 1)
+
+/* The last section. */
+#define PIO_INDIRECT (PIO_MAX_SECT - 1)
+/* This one is for MMIO(PCI) to keep compatibility */
+#define PIO_CPU_MMIO 0x00UL
+
+struct logic_pio_root {
+ struct list_head sec_head;
+ resource_size_t sec_min;
+ resource_size_t sec_max;
+};
+
+#if ((IO_SPACE_LIMIT + 1) & IO_SPACE_LIMIT)
+#error "(IO_SPACE_LIMIT + 1) must be power of 2!"
+#endif
+
+#define PIO_VAL_MASK (IO_SPACE_LIMIT >> PIO_SECT_BITS)
+#define PIO_VAL_BIT_LEN (ilog2(PIO_VAL_MASK) + 1)
+
+#define PIO_SECT_MIN(sec_id) ((sec_id) << PIO_VAL_BIT_LEN)
+#define PIO_SECT_MAX(sec_id) (PIO_SECT_MIN(sec_id) | PIO_VAL_MASK)
+
+#define PIO_SECT_ID(pio) ((pio >> PIO_VAL_BIT_LEN) & PIO_SECT_MASK)
+
+struct logic_pio_sect {
+ struct list_head list;
+ resource_size_t io_start;
+
+ struct logic_pio_hwaddr *hwpeer;
+};
+#define to_pio_sect(node) container_of(node, struct logic_pio_sect, list)
+
+struct logic_pio_hwaddr {
+ struct list_head list;
+ struct fwnode_handle *fwnode;
+ resource_size_t hw_start;
+ resource_size_t size; /* range size populated */
+ unsigned long flags;
+
+ struct logic_pio_sect *pio_peer;
+
+ void *devpara; /* private parameter of the host device */
+ struct hostio_ops *ops; /* ops operating on this node */
+};
+#define to_pio_hwaddr(node) container_of(node, struct logic_pio_hwaddr, list)
+
+struct hostio_ops {
+ u32 (*pfin)(void *devobj, unsigned long ptaddr, size_t dlen);
+ void (*pfout)(void *devobj, unsigned long ptaddr, u32 outval,
+ size_t dlen);
+ u32 (*pfins)(void *devobj, unsigned long ptaddr, void *inbuf,
+ size_t dlen, unsigned int count);
+ void (*pfouts)(void *devobj, unsigned long ptaddr,
+ const void *outbuf, size_t dlen, unsigned int count);
+};
+
+#ifdef CONFIG_INDIRECT_PIO
+#define LPC_MIN_BUS_RANGE 0x0
+
+/*
+ * The default maximal IO size for Hip06/Hip07 LPC bus.
+ * Defining the I/O range size as 0x400 here should be sufficient for
+ * all peripherals under the bus.
+ */
+#define LPC_BUS_IO_SIZE 0x400
+#endif
+
+extern u8 logic_inb(unsigned long addr);
+extern void logic_outb(u8 value, unsigned long addr);
+extern void logic_outw(u16 value, unsigned long addr);
+extern void logic_outl(u32 value, unsigned long addr);
+extern u16 logic_inw(unsigned long addr);
+extern u32 logic_inl(unsigned long addr);
+extern void logic_outb(u8 value, unsigned long addr);
+extern void logic_outw(u16 value, unsigned long addr);
+extern void logic_outl(u32 value, unsigned long addr);
+extern void logic_insb(unsigned long addr, void *buffer, unsigned int count);
+extern void logic_insl(unsigned long addr, void *buffer, unsigned int count);
+extern void logic_insw(unsigned long addr, void *buffer, unsigned int count);
+extern void logic_outsb(unsigned long addr, const void *buffer,
+ unsigned int count);
+extern void logic_outsw(unsigned long addr, const void *buffer,
+ unsigned int count);
+extern void logic_outsl(unsigned long addr, const void *buffer,
+ unsigned int count);
+#ifdef CONFIG_LOGIC_PIO
+extern struct logic_pio_hwaddr
+*find_io_range_by_fwnode(struct fwnode_handle *fwnode);
+
+extern unsigned long logic_pio_trans_hwaddr(struct fwnode_handle *fwnode,
+ resource_size_t hw_addr);
+#else
+static inline struct logic_pio_hwaddr
+*find_io_range_by_fwnode(struct fwnode_handle *fwnode)
+{
+ return NULL;
+}
+
+static inline unsigned long
+logic_pio_trans_hwaddr(struct fwnode_handle *fwnode, resource_size_t hw_addr)
+{
+ return -1;
+}
+#endif
+
+/*
+ * These are used by pci. As LOGIC_PIO is bound with PCI, no need to add dummy
+ * functions for them.
+ */
+extern struct logic_pio_hwaddr
+*logic_pio_register_range(struct logic_pio_hwaddr *newrange,
+ unsigned long align);
+
+extern resource_size_t logic_pio_to_hwaddr(unsigned long pio);
+
+extern unsigned long logic_pio_trans_cpuaddr(resource_size_t hw_addr);
+
+#endif /* __KERNEL__ */
+#endif /* __LINUX_LIBIO_H */
diff --git a/lib/Kconfig b/lib/Kconfig
index 0c8b78a..503c2e0 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -59,6 +59,32 @@ config ARCH_USE_CMPXCHG_LOCKREF
config ARCH_HAS_FAST_MULTIPLIER
bool

+config LOGIC_PIO
+ bool "Generic logical I/O management"
+ def_bool y if PCI && !X86 && !IA64 && !POWERPC
+ help
+ For some architectures, there are no IO space. To support the
+ accesses to legacy I/O devices on those architectures, kernel
+ implemented the memory mapped I/O mechanism based on bridge bus
+ supports. But for some buses which do not support MMIO, the
+ peripherals there should be accessed with device-specific way.
+ To abstract those different I/O accesses into unified I/O accessors,
+ this option provide a generic I/O space management way after mapping
+ the device I/O to system logical/fake I/O and help to hide all the
+ hardware detail.
+
+config INDIRECT_PIO
+ bool "Access I/O in non-MMIO mode" if LOGIC_PIO
+ help
+ On some platforms where no separate I/O space exist, there are I/O
+ hosts which can not be accessed in MMIO mode. Based on LOGIC_PIO
+ mechanism, the host-local I/O resource can be mapped into system
+ logic PIO space shared with MMIO hosts, such as PCI/PCIE, then system
+ can access the I/O devices with the mapped logic PIO through I/O
+ accessors.
+ This way has a little I/O performance cost. Please make sure your
+ devices really need this configure item enabled.
+
config CRC_CCITT
tristate "CRC-CCITT functions"
help
diff --git a/lib/Makefile b/lib/Makefile
index 320ac46a..26dcec0 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -77,6 +77,8 @@ obj-$(CONFIG_HAS_IOMEM) += iomap_copy.o devres.o
obj-$(CONFIG_CHECK_SIGNATURE) += check_signature.o
obj-$(CONFIG_DEBUG_LOCKING_API_SELFTESTS) += locking-selftest.o

+obj-$(CONFIG_LOGIC_PIO) += logic_pio.o
+
obj-$(CONFIG_GENERIC_HWEIGHT) += hweight.o

obj-$(CONFIG_BTREE) += btree.o
diff --git a/lib/logic_pio.c b/lib/logic_pio.c
new file mode 100644
index 0000000..ca247e3
--- /dev/null
+++ b/lib/logic_pio.c
@@ -0,0 +1,413 @@
+/*
+ * Copyright (C) 2017 Hisilicon Limited, All Rights Reserved.
+ * Author: Zhichang Yuan <yuanzhichang@xxxxxxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/of.h>
+#include <linux/io.h>
+#include <linux/mm.h>
+#include <linux/rculist.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+
+/* The unique hardware address list. */
+static LIST_HEAD(io_range_list);
+static DEFINE_MUTEX(io_range_mutex);
+
+/*
+ * These are the lists for PIO. The highest PIO_SECT_BITS of PIO is the index.
+ */
+static struct logic_pio_root logic_pio_root_list[PIO_MAX_SECT] = {
+#ifdef CONFIG_INDIRECT_PIO
+ /*
+ * At this moment, assign all the other logic PIO space to MMIO.
+ * If more elements added, please adjust the ending index and .sec_max;
+ * Please keep MMIO element started from index ZERO.
+ */
+ [PIO_CPU_MMIO ... PIO_INDIRECT - 1] = {
+ .sec_head = LIST_HEAD_INIT(logic_pio_root_list[PIO_CPU_MMIO].sec_head),
+ .sec_min = PIO_SECT_MIN(PIO_CPU_MMIO),
+ .sec_max = PIO_SECT_MAX(PIO_INDIRECT - 1),
+ },
+
+ /* The last element */
+ [PIO_INDIRECT] = {
+ .sec_head = LIST_HEAD_INIT(logic_pio_root_list[PIO_INDIRECT].sec_head),
+ .sec_min = PIO_SECT_MIN(PIO_INDIRECT),
+ .sec_max = PIO_SECT_MAX(PIO_INDIRECT),
+ },
+#else
+ [PIO_CPU_MMIO] = {
+ .sec_head = LIST_HEAD_INIT(logic_pio_root_list[PIO_CPU_MMIO].sec_head),
+ .sec_min = PIO_SECT_MIN(PIO_CPU_MMIO),
+ .sec_max = PIO_SECT_MAX(PIO_CPU_MMIO),
+ },
+
+#endif
+};
+
+/*
+ * Search a io_range registered which match the fwnode and addr.
+ *
+ * @fwnode: the host fwnode which must be valid;
+ * @start: the start hardware address of this search;
+ * @end: the end hardware address of this search. can be equal to @start;
+ *
+ * return NULL when there is no matched node; IS_ERR() means ERROR;
+ * valid virtual address represent a matched node was found.
+ */
+static struct logic_pio_hwaddr *
+logic_pio_find_range_byaddr(struct fwnode_handle *fwnode,
+ resource_size_t start, resource_size_t end)
+{
+ struct logic_pio_hwaddr *range;
+
+ list_for_each_entry_rcu(range, &io_range_list, list) {
+ if (!range->pio_peer) {
+ pr_warn("Invalid cpu addr node(%pa) in list!\n",
+ &range->hw_start);
+ continue;
+ }
+ if (range->fwnode != fwnode)
+ continue;
+ /* without any overlap with current range */
+ if (start >= range->hw_start + range->size ||
+ end < range->hw_start)
+ continue;
+ /* overlap is not supported now. */
+ if (start < range->hw_start ||
+ end >= range->hw_start + range->size)
+ return ERR_PTR(-EBUSY);
+ /* had been registered. */
+ return range;
+ }
+
+ return NULL;
+}
+
+
+static int logic_pio_alloc_range(struct logic_pio_root *root,
+ resource_size_t size, unsigned long align,
+ struct list_head **prev, resource_size_t *pio_alloc)
+{
+ struct logic_pio_sect *entry;
+ resource_size_t tmp_start;
+ resource_size_t idle_start, idle_end;
+
+ idle_start = root->sec_min;
+ *prev = &root->sec_head;
+ list_for_each_entry_rcu(entry, &root->sec_head, list) {
+ if (!entry->hwpeer ||
+ idle_start > entry->io_start) {
+ WARN(1, "skip an invalid io range during traversal!\n");
+ goto nextentry;
+ }
+ /* set the end edge. */
+ if (idle_start == entry->io_start) {
+ struct logic_pio_sect *next;
+
+ idle_start = entry->io_start + entry->hwpeer->size;
+ next = list_next_or_null_rcu(&root->sec_head,
+ &entry->list, struct logic_pio_sect, list);
+ if (next) {
+ entry = next;
+ } else {
+ *prev = &entry->list;
+ break;
+ }
+ }
+ idle_end = entry->io_start - 1;
+
+ /* contiguous range... */
+ if (idle_start > idle_end)
+ goto nextentry;
+
+ tmp_start = idle_start;
+ idle_start = ALIGN(idle_start, align);
+ if (idle_start >= tmp_start &&
+ idle_start + size <= idle_end) {
+ *prev = &entry->list;
+ *pio_alloc = idle_start;
+ return 0;
+ }
+
+nextentry:
+ idle_start = entry->io_start + entry->hwpeer->size;
+ *prev = &entry->list;
+ }
+ /* check the last free gap... */
+ idle_end = root->sec_max;
+
+ tmp_start = idle_start;
+ idle_start = ALIGN(idle_start, align);
+ if (idle_start >= tmp_start &&
+ idle_start + size <= idle_end) {
+ *pio_alloc = idle_start;
+ return 0;
+ }
+
+ return -EBUSY;
+}
+
+/*
+ * register a io range node in the io range list.
+ *
+ * @newrange: pointer to the io range to be registered.
+ *
+ * return 'newrange' when success, ERR_VALUE() is for failures.
+ * specially, return a valid pointer which is not equal to 'newrange' when
+ * the io range had been registered before.
+ */
+struct logic_pio_hwaddr
+*logic_pio_register_range(struct logic_pio_hwaddr *newrange,
+ unsigned long align)
+{
+ struct logic_pio_hwaddr *range;
+ struct logic_pio_sect *newsect;
+ resource_size_t pio_alloc;
+ struct list_head *prev, *hwprev;
+ unsigned long sect_id;
+ int err;
+
+ if (!newrange || !newrange->fwnode || !newrange->size)
+ return ERR_PTR(-EINVAL);
+
+ sect_id = newrange->flags;
+ if (sect_id >= PIO_MAX_SECT)
+ return ERR_PTR(-EINVAL);
+
+ mutex_lock(&io_range_mutex);
+ range = logic_pio_find_range_byaddr(newrange->fwnode,
+ newrange->hw_start,
+ newrange->hw_start + newrange->size - 1);
+ if (range) {
+ if (!IS_ERR(range))
+ pr_info("the request IO range had been registered!\n");
+ else
+ pr_err("registering IO[%pa - sz%pa) got failed!\n",
+ &newrange->hw_start, &newrange->size);
+ mutex_unlock(&io_range_mutex);
+ return range;
+ }
+
+ err = logic_pio_alloc_range(&logic_pio_root_list[sect_id],
+ newrange->size, align, &prev, &pio_alloc);
+ if (err) {
+ pr_err("can't find free %pa logical IO range!\n",
+ &newrange->size);
+ goto exitproc;
+ }
+
+ if (prev == &logic_pio_root_list[sect_id].sec_head) {
+ hwprev = &io_range_list;
+ } else {
+ newsect = to_pio_sect(prev);
+ hwprev = &newsect->hwpeer->list;
+ }
+
+ newsect = kzalloc(sizeof(*newsect), GFP_KERNEL);
+ if (!newsect) {
+ err = -ENOMEM;
+ goto exitproc;
+ }
+ newsect->io_start = pio_alloc;
+ newsect->hwpeer = newrange;
+ list_add_rcu(&newsect->list, prev);
+
+ newrange->pio_peer = newsect;
+ list_add_rcu(&newrange->list, hwprev);
+
+exitproc:
+ mutex_unlock(&io_range_mutex);
+ return err ? ERR_PTR(err) : newrange;
+}
+
+/*
+ * traverse the io_range_list to find the registered node whose device node
+ * and/or physical IO address match to.
+ */
+struct logic_pio_hwaddr *find_io_range_by_fwnode(struct fwnode_handle *fwnode)
+{
+ struct logic_pio_hwaddr *range;
+
+ list_for_each_entry_rcu(range, &io_range_list, list) {
+ if (range->fwnode == fwnode)
+ return range;
+ }
+ return NULL;
+}
+
+/*
+ * Translate the input logical pio to the corresponding hardware address.
+ * The input pio should be unique in the whole logical PIO space.
+ */
+resource_size_t logic_pio_to_hwaddr(unsigned long pio)
+{
+ struct logic_pio_sect *entry;
+ struct logic_pio_root *root;
+
+ /* The caller should check the section id is valid. */
+ root = &logic_pio_root_list[PIO_SECT_ID(pio)];
+ list_for_each_entry_rcu(entry, &root->sec_head, list) {
+ if (!entry->hwpeer) {
+ pr_warn("Invalid PIO entry(%pa) in list!\n",
+ &entry->io_start);
+ continue;
+ }
+ if (pio < entry->io_start)
+ break;
+
+ if (pio < entry->io_start + entry->hwpeer->size)
+ return pio - entry->io_start + entry->hwpeer->hw_start;
+ }
+
+ return -1;
+}
+
+/*
+ * This function is generic for translating a hardware address to logical PIO.
+ * @hw_addr: the hardware address of host, can be CPU address or host-local
+ * address;
+ */
+unsigned long
+logic_pio_trans_hwaddr(struct fwnode_handle *fwnode, resource_size_t addr)
+{
+ struct logic_pio_hwaddr *range;
+
+ range = logic_pio_find_range_byaddr(fwnode, addr, addr);
+ if (!range)
+ return -1;
+
+ return addr - range->hw_start + range->pio_peer->io_start;
+}
+
+unsigned long
+logic_pio_trans_cpuaddr(resource_size_t addr)
+{
+ struct logic_pio_hwaddr *range;
+
+ list_for_each_entry_rcu(range, &io_range_list, list) {
+ if (!range->pio_peer) {
+ pr_warn("Invalid cpu addr node(%pa) in list!\n",
+ &range->hw_start);
+ continue;
+ }
+ if (range->flags != PIO_CPU_MMIO)
+ continue;
+ if (addr >= range->hw_start &&
+ addr < range->hw_start + range->size)
+ return addr - range->hw_start +
+ range->pio_peer->io_start;
+ }
+ return -1;
+}
+
+#if defined(CONFIG_INDIRECT_PIO) && defined(PCI_IOBASE)
+static struct logic_pio_hwaddr *find_io_range(unsigned long pio)
+{
+ struct logic_pio_sect *entry;
+ struct logic_pio_root *root;
+
+ root = &logic_pio_root_list[PIO_SECT_ID(pio)];
+ if (pio < root->sec_min || pio > root->sec_max)
+ return NULL;
+ /*
+ * non indirectIO section, no need to convert the addr. Jump to mmio ops
+ * directly.
+ */
+ if (&root->sec_head == &logic_pio_root_list[PIO_CPU_MMIO].sec_head)
+ return NULL;
+ list_for_each_entry_rcu(entry, &root->sec_head, list) {
+ if (!entry->hwpeer) {
+ pr_warn("Invalid PIO entry(%pa) in list!\n",
+ &entry->io_start);
+ continue;
+ }
+ if (pio < entry->io_start)
+ break;
+
+ if (pio < entry->io_start + entry->hwpeer->size)
+ return entry->hwpeer;
+ }
+
+ return NULL;
+}
+
+#define BUILD_LOGIC_IO(bw, type) \
+type logic_in##bw(unsigned long addr) \
+{ \
+ struct logic_pio_hwaddr *entry = find_io_range(addr); \
+ \
+ if (entry && entry->ops) \
+ return entry->ops->pfin(entry->devpara, \
+ addr, sizeof(type)); \
+ return read##bw(PCI_IOBASE + addr); \
+} \
+ \
+void logic_out##bw(type value, unsigned long addr) \
+{ \
+ struct logic_pio_hwaddr *entry = find_io_range(addr); \
+ \
+ if (entry && entry->ops) \
+ entry->ops->pfout(entry->devpara, \
+ addr, value, sizeof(type)); \
+ else \
+ write##bw(value, PCI_IOBASE + addr); \
+} \
+ \
+void logic_ins##bw(unsigned long addr, void *buffer, unsigned int count)\
+{ \
+ struct logic_pio_hwaddr *entry = find_io_range(addr); \
+ \
+ if (entry && entry->ops) \
+ entry->ops->pfins(entry->devpara, \
+ addr, buffer, sizeof(type), count); \
+ else \
+ reads##bw(PCI_IOBASE + addr, buffer, count); \
+} \
+ \
+void logic_outs##bw(unsigned long addr, const void *buffer, \
+ unsigned int count) \
+{ \
+ struct logic_pio_hwaddr *entry = find_io_range(addr); \
+ \
+ if (entry && entry->ops) \
+ entry->ops->pfouts(entry->devpara, \
+ addr, buffer, sizeof(type), count); \
+ else \
+ writes##bw(PCI_IOBASE + addr, buffer, count); \
+}
+
+BUILD_LOGIC_IO(b, u8)
+
+EXPORT_SYMBOL(logic_inb);
+EXPORT_SYMBOL(logic_outb);
+EXPORT_SYMBOL(logic_insb);
+EXPORT_SYMBOL(logic_outsb);
+
+BUILD_LOGIC_IO(w, u16)
+
+EXPORT_SYMBOL(logic_inw);
+EXPORT_SYMBOL(logic_outw);
+EXPORT_SYMBOL(logic_insw);
+EXPORT_SYMBOL(logic_outsw);
+
+BUILD_LOGIC_IO(l, u32)
+
+EXPORT_SYMBOL(logic_inl);
+EXPORT_SYMBOL(logic_outl);
+EXPORT_SYMBOL(logic_insl);
+EXPORT_SYMBOL(logic_outsl);
+#endif /* CONFIG_INDIRECT_PIO && PCI_IOBASE */
--
1.9.1