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 logical PIO, then 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 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/io.h | 1 + include/linux/libio.h | 94 ++++++++++++++ lib/Kconfig | 14 ++ lib/Makefile | 2 + lib/libio.c | 324 +++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 485 insertions(+) create mode 100644 include/linux/libio.h create mode 100644 lib/libio.c diff --git a/include/asm-generic/io.h b/include/asm-generic/io.h index 7ef015e..91a7ed4 100644 --- a/include/asm-generic/io.h +++ b/include/asm-generic/io.h @@ -21,6 +21,8 @@ #include <asm-generic/pci_iomap.h> +#include <linux/libio.h> + #ifndef mmiowb #define mmiowb() do {} while (0) #endif @@ -358,51 +360,75 @@ static inline void writesq(volatile void __iomem *addr, const void *buffer, */ #ifndef inb +#ifdef CONFIG_LIBIO +#define inb libio_inb +#else #define inb inb static inline u8 inb(unsigned long addr) { return readb(PCI_IOBASE + addr); } +#endif /* CONFIG_LIBIO */ #endif #ifndef inw +#ifdef CONFIG_LIBIO +#define inw libio_inw +#else #define inw inw static inline u16 inw(unsigned long addr) { return readw(PCI_IOBASE + addr); } +#endif /* CONFIG_LIBIO */ #endif #ifndef inl +#ifdef CONFIG_LIBIO +#define inl libio_inl +#else #define inl inl static inline u32 inl(unsigned long addr) { return readl(PCI_IOBASE + addr); } +#endif /* CONFIG_LIBIO */ #endif #ifndef outb +#ifdef CONFIG_LIBIO +#define outb libio_outb +#else #define outb outb static inline void outb(u8 value, unsigned long addr) { writeb(value, PCI_IOBASE + addr); } +#endif /* CONFIG_LIBIO */ #endif #ifndef outw +#ifdef CONFIG_LIBIO +#define outw libio_outw +#else #define outw outw static inline void outw(u16 value, unsigned long addr) { writew(value, PCI_IOBASE + addr); } +#endif /* CONFIG_LIBIO */ #endif #ifndef outl +#ifdef CONFIG_LIBIO +#define outl libio_outl +#else #define outl outl static inline void outl(u32 value, unsigned long addr) { writel(value, PCI_IOBASE + addr); } +#endif /* CONFIG_LIBIO */ #endif #ifndef inb_p @@ -459,54 +485,78 @@ static inline void outl_p(u32 value, unsigned long addr) */ #ifndef insb +#ifdef CONFIG_LIBIO +#define insb libio_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_LIBIO */ #endif #ifndef insw +#ifdef CONFIG_LIBIO +#define insw libio_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_LIBIO */ #endif #ifndef insl +#ifdef CONFIG_LIBIO +#define insl libio_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_LIBIO */ #endif #ifndef outsb +#ifdef CONFIG_LIBIO +#define outsb libio_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_LIBIO */ #endif #ifndef outsw +#ifdef CONFIG_LIBIO +#define outsw libio_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_LIBIO */ #endif #ifndef outsl +#ifdef CONFIG_LIBIO +#define outsl libio_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_LIBIO */ #endif #ifndef insb_p diff --git a/include/linux/io.h b/include/linux/io.h index 82ef36e..51ec1aa 100644 --- a/include/linux/io.h +++ b/include/linux/io.h @@ -24,6 +24,7 @@ #include <linux/err.h> #include <asm/io.h> #include <asm/page.h> +#include <linux/libio.h> struct device; struct resource; diff --git a/include/linux/libio.h b/include/linux/libio.h new file mode 100644 index 0000000..91038aa --- /dev/null +++ b/include/linux/libio.h @@ -0,0 +1,94 @@ +/* + * 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> + +/* This is compatible to PCI MMIO. */ +#define IO_CPU_MMIO 0x01 +/* All hosts where there are no CPU addr */ +#define IO_HOST_INDIRECT 0x02 + +struct libio_range { + struct list_head list; + struct fwnode_handle *node; + resource_size_t size; /* range size populated */ + resource_size_t io_start; /* logical pio start. inclusive */ + resource_size_t hw_start; + unsigned long flags; + void *devpara; /* private parameter of the host device */ + struct libio_ops *ops; /* ops operating on this node */ +}; + +struct libio_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); +}; + +extern u8 libio_inb(unsigned long addr); +extern void libio_outb(u8 value, unsigned long addr); +extern void libio_outw(u16 value, unsigned long addr); +extern void libio_outl(u32 value, unsigned long addr); +extern u16 libio_inw(unsigned long addr); +extern u32 libio_inl(unsigned long addr); +extern void libio_outb(u8 value, unsigned long addr); +extern void libio_outw(u16 value, unsigned long addr); +extern void libio_outl(u32 value, unsigned long addr); +extern void libio_insb(unsigned long addr, void *buffer, unsigned int count); +extern void libio_insl(unsigned long addr, void *buffer, unsigned int count); +extern void libio_insw(unsigned long addr, void *buffer, unsigned int count); +extern void libio_outsb(unsigned long addr, const void *buffer, + unsigned int count); +extern void libio_outsw(unsigned long addr, const void *buffer, + unsigned int count); +extern void libio_outsl(unsigned long addr, const void *buffer, + unsigned int count); +#ifdef CONFIG_LIBIO +extern struct libio_range +*find_io_range_from_fwnode(struct fwnode_handle *fwnode); +extern unsigned long libio_translate_hwaddr(struct fwnode_handle *fwnode, + resource_size_t hw_addr); +#else +static inline struct libio_range +*find_io_range_from_fwnode(struct fwnode_handle *fwnode) +{ + return NULL; +} + +static inline unsigned long libio_translate_hwaddr(struct fwnode_handle *fwnode, + resource_size_t hw_addr) +{ + return -1; +} +#endif + +extern struct libio_range *register_libio_range(struct libio_range *newrange); +extern resource_size_t libio_to_hwaddr(unsigned long pio); + +extern unsigned long libio_translate_cpuaddr(resource_size_t hw_addr); + +#endif /* __KERNEL__ */ +#endif /* __LINUX_LIBIO_H */ diff --git a/lib/Kconfig b/lib/Kconfig index 0c8b78a..ba9787d5 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -59,6 +59,20 @@ config ARCH_USE_CMPXCHG_LOCKREF config ARCH_HAS_FAST_MULTIPLIER bool +config LIBIO + bool "Generic logical IO management" + def_bool y if PCI && (ARM || ARC || UNICORE32 || SPARC || S390 || CRIS || BLACKFIN || XTENSA || ARM64) + 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 CRC_CCITT tristate "CRC-CCITT functions" help diff --git a/lib/Makefile b/lib/Makefile index 320ac46a..9c4cd24 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_LIBIO) += libio.o + obj-$(CONFIG_GENERIC_HWEIGHT) += hweight.o obj-$(CONFIG_BTREE) += btree.o diff --git a/lib/libio.c b/lib/libio.c new file mode 100644 index 0000000..e42f50b --- /dev/null +++ b/lib/libio.c @@ -0,0 +1,324 @@ +/* + * 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> + +/* A list of all the IO hosts registered. ONLY THE HOST nodes. */ +static LIST_HEAD(io_range_list); +static DEFINE_MUTEX(io_range_mutex); + +/* + * allocate a free range for this registration. + * + * @new_range: point to the node awaiting this registration. + * part of the fields are as input parameters. This node + * is allocated and initialized by caller; + * @prev: points to the last node before the return; + * + * return 0 for success, other are fail. + */ +static int libio_alloc_range(struct libio_range *new_range, + struct list_head **prev) +{ + struct libio_range *entry; + unsigned long align = 1; + unsigned long tmp_start; + unsigned long idle_start, idle_end; + + if (new_range->flags & IO_CPU_MMIO) + align = PAGE_SIZE; + idle_start = 0; + *prev = &io_range_list; + list_for_each_entry_rcu(entry, &io_range_list, list) { + if (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 libio_range *next; + + idle_start = entry->io_start + entry->size; + next = list_next_or_null_rcu(&io_range_list, + &entry->list, struct libio_range, 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 + new_range->size <= idle_end) { + new_range->io_start = idle_start; + *prev = &entry->list; + return 0; + } + +nextentry: + idle_start = entry->io_start + entry->size; + *prev = &entry->list; + } + /* check the last free gap... */ + idle_end = IO_SPACE_LIMIT; + + tmp_start = idle_start; + idle_start = ALIGN(idle_start, align); + if (idle_start >= tmp_start && + idle_start + new_range->size <= idle_end) { + new_range->io_start = idle_start; + return 0; + } + + return -EBUSY; +} + +/* + * traverse the io_range_list to find the registered node whose device node + * and/or physical IO address match to. + */ +struct libio_range *find_io_range_from_fwnode(struct fwnode_handle *fwnode) +{ + struct libio_range *range; + + list_for_each_entry_rcu(range, &io_range_list, list) { + if (range->node == fwnode) + return range; + } + return NULL; +} + +/* + * 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 libio_range * +libio_find_range_byaddr(struct fwnode_handle *fwnode, + resource_size_t start, resource_size_t end) +{ + struct libio_range *entry; + + list_for_each_entry_rcu(entry, &io_range_list, list) { + if (entry->node != fwnode) + continue; + /* without any overlap with current range */ + if (start >= entry->hw_start + entry->size || + end < entry->hw_start) + continue; + /* overlap is not supported now. */ + if (start < entry->hw_start || + end >= entry->hw_start + entry->size) + return ERR_PTR(-EBUSY); + /* had been registered. */ + return entry; + } + + return NULL; +} + +/* + * 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 libio_range *register_libio_range(struct libio_range *newrange) +{ + int err; + struct libio_range *range; + struct list_head *prev; + + if (!newrange || !newrange->node || !newrange->size) + return ERR_PTR(-EINVAL); + + mutex_lock(&io_range_mutex); + range = libio_find_range_byaddr(newrange->node, 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); + return range; + } + + err = libio_alloc_range(newrange, &prev); + if (!err) + /* the bus IO range list is ordered by pio. */ + list_add_rcu(&newrange->list, prev); + else + pr_err("can't find free %pa logical IO range!\n", + &newrange->size); + + mutex_unlock(&io_range_mutex); + return err ? ERR_PTR(err) : newrange; +} + +/* + * 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 libio_to_hwaddr(unsigned long pio) +{ + struct libio_range *range; + + list_for_each_entry_rcu(range, &io_range_list, list) { + if (pio < range->io_start) + break; + + if (pio < range->io_start + range->size) + return pio - range->io_start + range->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 +libio_translate_hwaddr(struct fwnode_handle *fwnode, resource_size_t addr) +{ + struct libio_range *range; + + range = libio_find_range_byaddr(fwnode, addr, addr); + if (!range) + return -1; + + return addr - range->hw_start + range->io_start; +} + +unsigned long +libio_translate_cpuaddr(resource_size_t addr) +{ + struct libio_range *range; + + list_for_each_entry_rcu(range, &io_range_list, list) { + if (!(range->flags & IO_CPU_MMIO)) + continue; + if (addr >= range->hw_start && + addr < range->hw_start + range->size) + return addr - range->hw_start + range->io_start; + } + return -1; +} + +#ifdef PCI_IOBASE +static struct libio_range *find_io_range(unsigned long pio) +{ + struct libio_range *range; + + list_for_each_entry_rcu(range, &io_range_list, list) { + if (range->io_start > pio) + return NULL; + if (pio < range->io_start + range->size) + return range; + } + return NULL; +} + +#define BUILD_IO(bw, type) \ +type libio_in##bw(unsigned long addr) \ +{ \ + struct libio_range *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 libio_out##bw(type value, unsigned long addr) \ +{ \ + struct libio_range *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 libio_ins##bw(unsigned long addr, void *buffer, unsigned int count)\ +{ \ + struct libio_range *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 libio_outs##bw(unsigned long addr, const void *buffer, \ + unsigned int count) \ +{ \ + struct libio_range *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_IO(b, u8) + +EXPORT_SYMBOL(libio_inb); +EXPORT_SYMBOL(libio_outb); +EXPORT_SYMBOL(libio_insb); +EXPORT_SYMBOL(libio_outsb); + +BUILD_IO(w, u16) + +EXPORT_SYMBOL(libio_inw); +EXPORT_SYMBOL(libio_outw); +EXPORT_SYMBOL(libio_insw); +EXPORT_SYMBOL(libio_outsw); + +BUILD_IO(l, u32) + +EXPORT_SYMBOL(libio_inl); +EXPORT_SYMBOL(libio_outl); +EXPORT_SYMBOL(libio_insl); +EXPORT_SYMBOL(libio_outsl); +#endif /* PCI_IOBASE */ -- 1.9.1 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html