From 031e3601869c815582ca1d49d1ff73de58e446b0 Mon Sep 17 00:00:00 2001 From: Zhichang Yuan Date: Thu, 15 Mar 2018 02:15:50 +0800 Subject: lib: Add generic PIO mapping method 41f8bba7f555 ("of/pci: Add pci_register_io_range() and pci_pio_to_address()") added support for PCI I/O space mapped into CPU physical memory space. With that support, the I/O ranges configured for PCI/PCIe hosts on some architectures can be mapped to logical PIO and converted easily between CPU address and the corresponding logical PIO. Based on this, PCI I/O port space can be accessed via in/out accessors that use memory read/write. But on some platforms, there are bus hosts that access I/O port space with host-local I/O port addresses rather than memory addresses. Add a more generic I/O mapping method to support those devices. With 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. Tested-by: dann frazier Signed-off-by: Zhichang Yuan Signed-off-by: Gabriele Paoloni Signed-off-by: John Garry [bhelgaas: remove -EFAULT return from logic_pio_register_range() per https://lkml.kernel.org/r/20180403143909.GA21171@ulmo, fix NULL pointer checking per https://lkml.kernel.org/r/20180403211505.GA29612@embeddedor.com] Signed-off-by: Bjorn Helgaas Reviewed-by: Andy Shevchenko --- include/asm-generic/io.h | 2 + include/linux/logic_pio.h | 123 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 include/linux/logic_pio.h (limited to 'include') diff --git a/include/asm-generic/io.h b/include/asm-generic/io.h index b4531e3b2120..b7996a79d64b 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 + /* * {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 diff --git a/include/linux/logic_pio.h b/include/linux/logic_pio.h new file mode 100644 index 000000000000..cbd9d8495690 --- /dev/null +++ b/include/linux/logic_pio.h @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2017 HiSilicon Limited, All Rights Reserved. + * Author: Gabriele Paoloni + * Author: Zhichang Yuan + */ + +#ifndef __LINUX_LOGIC_PIO_H +#define __LINUX_LOGIC_PIO_H + +#include + +enum { + LOGIC_PIO_INDIRECT, /* Indirect IO flag */ + LOGIC_PIO_CPU_MMIO, /* Memory-mapped IO flag */ +}; + +struct logic_pio_hwaddr { + struct list_head list; + struct fwnode_handle *fwnode; + resource_size_t hw_start; + resource_size_t io_start; + resource_size_t size; /* range size populated */ + unsigned long flags; + + void *hostdata; + const struct logic_pio_host_ops *ops; +}; + +struct logic_pio_host_ops { + u32 (*in)(void *hostdata, unsigned long addr, size_t dwidth); + void (*out)(void *hostdata, unsigned long addr, u32 val, + size_t dwidth); + u32 (*ins)(void *hostdata, unsigned long addr, void *buffer, + size_t dwidth, unsigned int count); + void (*outs)(void *hostdata, unsigned long addr, const void *buffer, + size_t dwidth, unsigned int count); +}; + +#ifdef CONFIG_INDIRECT_PIO +u8 logic_inb(unsigned long addr); +void logic_outb(u8 value, unsigned long addr); +void logic_outw(u16 value, unsigned long addr); +void logic_outl(u32 value, unsigned long addr); +u16 logic_inw(unsigned long addr); +u32 logic_inl(unsigned long addr); +void logic_outb(u8 value, unsigned long addr); +void logic_outw(u16 value, unsigned long addr); +void logic_outl(u32 value, unsigned long addr); +void logic_insb(unsigned long addr, void *buffer, unsigned int count); +void logic_insl(unsigned long addr, void *buffer, unsigned int count); +void logic_insw(unsigned long addr, void *buffer, unsigned int count); +void logic_outsb(unsigned long addr, const void *buffer, unsigned int count); +void logic_outsw(unsigned long addr, const void *buffer, unsigned int count); +void logic_outsl(unsigned long addr, const void *buffer, unsigned int count); + +#ifndef inb +#define inb logic_inb +#endif + +#ifndef inw +#define inw logic_inw +#endif + +#ifndef inl +#define inl logic_inl +#endif + +#ifndef outb +#define outb logic_outb +#endif + +#ifndef outw +#define outw logic_outw +#endif + +#ifndef outl +#define outl logic_outl +#endif + +#ifndef insb +#define insb logic_insb +#endif + +#ifndef insw +#define insw logic_insw +#endif + +#ifndef insl +#define insl logic_insl +#endif + +#ifndef outsb +#define outsb logic_outsb +#endif + +#ifndef outsw +#define outsw logic_outsw +#endif + +#ifndef outsl +#define outsl logic_outsl +#endif + +/* + * We reserve 0x4000 bytes for Indirect IO as so far this library is only + * used by the HiSilicon LPC Host. If needed, we can reserve a wider IO + * area by redefining the macro below. + */ +#define PIO_INDIRECT_SIZE 0x4000 +#define MMIO_UPPER_LIMIT (IO_SPACE_LIMIT - PIO_INDIRECT_SIZE) +#else +#define MMIO_UPPER_LIMIT IO_SPACE_LIMIT +#endif /* CONFIG_INDIRECT_PIO */ + +struct logic_pio_hwaddr *find_io_range_by_fwnode(struct fwnode_handle *fwnode); +unsigned long logic_pio_trans_hwaddr(struct fwnode_handle *fwnode, + resource_size_t hw_addr, resource_size_t size); +int logic_pio_register_range(struct logic_pio_hwaddr *newrange); +resource_size_t logic_pio_to_hwaddr(unsigned long pio); +unsigned long logic_pio_trans_cpuaddr(resource_size_t hw_addr); + +#endif /* __LINUX_LOGIC_PIO_H */ -- cgit v1.2.3 From fcfaab30933bd151bd8cb4dd07b3f11d885bb611 Mon Sep 17 00:00:00 2001 From: Gabriele Paoloni Date: Thu, 15 Mar 2018 02:15:52 +0800 Subject: PCI: Add fwnode handler as input param of pci_register_io_range() In preparation for having the PCI MMIO helpers use the new generic I/O space management (logical PIO) we need to add the fwnode handler as an extra input parameter. Changes the signature of pci_register_io_range() and its callers as needed. Tested-by: dann frazier Signed-off-by: Gabriele Paoloni Signed-off-by: Bjorn Helgaas Reviewed-by: Andy Shevchenko Acked-by: Rob Herring --- drivers/acpi/pci_root.c | 8 +++++--- drivers/of/address.c | 4 +++- drivers/pci/pci.c | 3 ++- include/linux/pci.h | 3 ++- 4 files changed, 12 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index 6fc204a52493..12134797b374 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -729,7 +729,8 @@ next: } } -static void acpi_pci_root_remap_iospace(struct resource_entry *entry) +static void acpi_pci_root_remap_iospace(struct fwnode_handle *fwnode, + struct resource_entry *entry) { #ifdef PCI_IOBASE struct resource *res = entry->res; @@ -738,7 +739,7 @@ static void acpi_pci_root_remap_iospace(struct resource_entry *entry) resource_size_t length = resource_size(res); unsigned long port; - if (pci_register_io_range(cpu_addr, length)) + if (pci_register_io_range(fwnode, cpu_addr, length)) goto err; port = pci_address_to_pio(cpu_addr); @@ -780,7 +781,8 @@ int acpi_pci_probe_root_resources(struct acpi_pci_root_info *info) else { resource_list_for_each_entry_safe(entry, tmp, list) { if (entry->res->flags & IORESOURCE_IO) - acpi_pci_root_remap_iospace(entry); + acpi_pci_root_remap_iospace(&device->fwnode, + entry); if (entry->res->flags & IORESOURCE_DISABLED) resource_list_destroy_entry(entry); diff --git a/drivers/of/address.c b/drivers/of/address.c index ce4d3d8b85de..cdf047b6d0a2 100644 --- a/drivers/of/address.c +++ b/drivers/of/address.c @@ -2,6 +2,7 @@ #define pr_fmt(fmt) "OF: " fmt #include +#include #include #include #include @@ -333,7 +334,8 @@ int of_pci_range_to_resource(struct of_pci_range *range, if (res->flags & IORESOURCE_IO) { unsigned long port; - err = pci_register_io_range(range->cpu_addr, range->size); + err = pci_register_io_range(&np->fwnode, range->cpu_addr, + range->size); if (err) goto invalid_range; port = pci_address_to_pio(range->cpu_addr); diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 4666a016356e..07290a31370c 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -3455,7 +3455,8 @@ static DEFINE_SPINLOCK(io_range_lock); * Record the PCI IO range (expressed as CPU physical address + size). * Return a negative value if an error has occured, zero otherwise */ -int pci_register_io_range(phys_addr_t addr, resource_size_t size) +int pci_register_io_range(struct fwnode_handle *fwnode, phys_addr_t addr, + resource_size_t size) { int err = 0; diff --git a/include/linux/pci.h b/include/linux/pci.h index 024a1beda008..be686fd87abb 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -1226,7 +1226,8 @@ int __must_check pci_bus_alloc_resource(struct pci_bus *bus, void *alignf_data); -int pci_register_io_range(phys_addr_t addr, resource_size_t size); +int pci_register_io_range(struct fwnode_handle *fwnode, phys_addr_t addr, + resource_size_t size); unsigned long pci_address_to_pio(phys_addr_t addr); phys_addr_t pci_pio_to_address(unsigned long pio); int pci_remap_iospace(const struct resource *res, phys_addr_t phys_addr); -- cgit v1.2.3 From 5745392e0c2b78e0d73203281d5c42cbd6993194 Mon Sep 17 00:00:00 2001 From: Zhichang Yuan Date: Thu, 15 Mar 2018 02:15:53 +0800 Subject: PCI: Apply the new generic I/O management on PCI IO hosts After introducing the new generic I/O space management (Logical PIO), the original PCI MMIO relevant helpers need to be updated based on the new interfaces defined in logical PIO. Adapt the corresponding code to match the changes introduced by logical PIO. Tested-by: dann frazier Signed-off-by: Zhichang Yuan Signed-off-by: Gabriele Paoloni Signed-off-by: Arnd Bergmann # earlier draft Signed-off-by: Bjorn Helgaas Reviewed-by: Andy Shevchenko --- drivers/pci/pci.c | 92 +++++++++--------------------------------------- include/asm-generic/io.h | 2 +- 2 files changed, 18 insertions(+), 76 deletions(-) (limited to 'include') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 07290a31370c..83a263fc9246 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -3440,17 +3441,6 @@ int pci_request_regions_exclusive(struct pci_dev *pdev, const char *res_name) } EXPORT_SYMBOL(pci_request_regions_exclusive); -#ifdef PCI_IOBASE -struct io_range { - struct list_head list; - phys_addr_t start; - resource_size_t size; -}; - -static LIST_HEAD(io_range_list); -static DEFINE_SPINLOCK(io_range_lock); -#endif - /* * Record the PCI IO range (expressed as CPU physical address + size). * Return a negative value if an error has occured, zero otherwise @@ -3458,51 +3448,28 @@ static DEFINE_SPINLOCK(io_range_lock); int pci_register_io_range(struct fwnode_handle *fwnode, phys_addr_t addr, resource_size_t size) { - int err = 0; - + int ret = 0; #ifdef PCI_IOBASE - struct io_range *range; - resource_size_t allocated_size = 0; - - /* check if the range hasn't been previously recorded */ - spin_lock(&io_range_lock); - list_for_each_entry(range, &io_range_list, list) { - if (addr >= range->start && addr + size <= range->start + size) { - /* range already registered, bail out */ - goto end_register; - } - allocated_size += range->size; - } + struct logic_pio_hwaddr *range; - /* range not registed yet, check for available space */ - if (allocated_size + size - 1 > IO_SPACE_LIMIT) { - /* if it's too big check if 64K space can be reserved */ - if (allocated_size + SZ_64K - 1 > IO_SPACE_LIMIT) { - err = -E2BIG; - goto end_register; - } - - size = SZ_64K; - pr_warn("Requested IO range too big, new size set to 64K\n"); - } + if (!size || addr + size < addr) + return -EINVAL; - /* add the range to the list */ range = kzalloc(sizeof(*range), GFP_ATOMIC); - if (!range) { - err = -ENOMEM; - goto end_register; - } + if (!range) + return -ENOMEM; - range->start = addr; + range->fwnode = fwnode; range->size = size; + range->hw_start = addr; + range->flags = LOGIC_PIO_CPU_MMIO; - list_add_tail(&range->list, &io_range_list); - -end_register: - spin_unlock(&io_range_lock); + ret = logic_pio_register_range(range); + if (ret) + kfree(range); #endif - return err; + return ret; } phys_addr_t pci_pio_to_address(unsigned long pio) @@ -3510,21 +3477,10 @@ phys_addr_t pci_pio_to_address(unsigned long pio) phys_addr_t address = (phys_addr_t)OF_BAD_ADDR; #ifdef PCI_IOBASE - struct io_range *range; - resource_size_t allocated_size = 0; - - if (pio > IO_SPACE_LIMIT) + if (pio >= MMIO_UPPER_LIMIT) return address; - spin_lock(&io_range_lock); - list_for_each_entry(range, &io_range_list, list) { - if (pio >= allocated_size && pio < allocated_size + range->size) { - address = range->start + pio - allocated_size; - break; - } - allocated_size += range->size; - } - spin_unlock(&io_range_lock); + address = logic_pio_to_hwaddr(pio); #endif return address; @@ -3533,21 +3489,7 @@ phys_addr_t pci_pio_to_address(unsigned long pio) unsigned long __weak pci_address_to_pio(phys_addr_t address) { #ifdef PCI_IOBASE - struct io_range *res; - resource_size_t offset = 0; - unsigned long addr = -1; - - spin_lock(&io_range_lock); - list_for_each_entry(res, &io_range_list, list) { - if (address >= res->start && address < res->start + res->size) { - addr = address - res->start + offset; - break; - } - offset += res->size; - } - spin_unlock(&io_range_lock); - - return addr; + return logic_pio_trans_cpuaddr(address); #else if (address > IO_SPACE_LIMIT) return (unsigned long)-1; diff --git a/include/asm-generic/io.h b/include/asm-generic/io.h index b7996a79d64b..5a5993166654 100644 --- a/include/asm-generic/io.h +++ b/include/asm-generic/io.h @@ -901,7 +901,7 @@ static inline void iounmap(void __iomem *addr) #define ioport_map ioport_map static inline void __iomem *ioport_map(unsigned long port, unsigned int nr) { - return PCI_IOBASE + (port & IO_SPACE_LIMIT); + return PCI_IOBASE + (port & MMIO_UPPER_LIMIT); } #endif -- cgit v1.2.3 From d87fb0917a073d71300b2b31b3773f6690bd1712 Mon Sep 17 00:00:00 2001 From: John Garry Date: Thu, 15 Mar 2018 02:15:56 +0800 Subject: ACPI / scan: Rename acpi_is_serial_bus_slave() for more general use Currently the ACPI scan has special handling for serial bus slaves, in that it makes it the responsibility of the slave device's parent to enumerate the device. To support other types of slave devices which require the same special handling but where the bus is not strictly a serial bus, such as devices on the HiSilicon LPC controller bus, rename acpi_is_serial_bus_slave() to acpi_device_enumeration_by_parent(), so that the name can fit the wider purpose. Also rename the associated device flag acpi_device_flags.serial_bus_slave to .enumeration_by_parent. Signed-off-by: John Garry Signed-off-by: Bjorn Helgaas Acked-by: Rafael J. Wysocki --- drivers/acpi/scan.c | 19 ++++++++++--------- include/acpi/acpi_bus.h | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) (limited to 'include') diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 8e63d937babb..f9e790479059 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -1524,7 +1524,7 @@ static int acpi_check_serial_bus_slave(struct acpi_resource *ares, void *data) return -1; } -static bool acpi_is_serial_bus_slave(struct acpi_device *device) +static bool acpi_device_enumeration_by_parent(struct acpi_device *device) { struct list_head resource_list; bool is_serial_bus_slave = false; @@ -1560,7 +1560,8 @@ void acpi_init_device_object(struct acpi_device *device, acpi_handle handle, acpi_bus_get_flags(device); device->flags.match_driver = false; device->flags.initialized = true; - device->flags.serial_bus_slave = acpi_is_serial_bus_slave(device); + device->flags.enumeration_by_parent = + acpi_device_enumeration_by_parent(device); acpi_device_clear_enumerated(device); device_initialize(&device->dev); dev_set_uevent_suppress(&device->dev, true); @@ -1858,10 +1859,10 @@ static acpi_status acpi_bus_check_add(acpi_handle handle, u32 lvl_not_used, static void acpi_default_enumeration(struct acpi_device *device) { /* - * Do not enumerate SPI/I2C/UART slaves as they will be enumerated by - * their respective parents. + * Do not enumerate devices with enumeration_by_parent flag set as + * they will be enumerated by their respective parents. */ - if (!device->flags.serial_bus_slave) { + if (!device->flags.enumeration_by_parent) { acpi_create_platform_device(device, NULL); acpi_device_set_enumerated(device); } else { @@ -1958,7 +1959,7 @@ static void acpi_bus_attach(struct acpi_device *device) return; device->flags.match_driver = true; - if (ret > 0 && !device->flags.serial_bus_slave) { + if (ret > 0 && !device->flags.enumeration_by_parent) { acpi_device_set_enumerated(device); goto ok; } @@ -1967,10 +1968,10 @@ static void acpi_bus_attach(struct acpi_device *device) if (ret < 0) return; - if (!device->pnp.type.platform_id && !device->flags.serial_bus_slave) - acpi_device_set_enumerated(device); - else + if (device->pnp.type.platform_id || device->flags.enumeration_by_parent) acpi_default_enumeration(device); + else + acpi_device_set_enumerated(device); ok: list_for_each_entry(child, &device->children, node) diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index c9608b0b80c6..ba4dd54f2c82 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -215,7 +215,7 @@ struct acpi_device_flags { u32 of_compatible_ok:1; u32 coherent_dma:1; u32 cca_seen:1; - u32 serial_bus_slave:1; + u32 enumeration_by_parent:1; u32 reserved:19; }; -- cgit v1.2.3