From: Al Stone ahs3@redhat.com
The following patches are pretty much the same core as used for the Seattle topic branch. There are a few additional APM X-Gene specific patches.
These are not final by any stretch of the imagination. As with the Seattle patches, there is much work to be done for PCI (especially since the Mustang hardware is a very special case), and it is a known the the network card does not yet work properly.
So why commit these patches? Simply put, to record the current progress and make visible the current work on Mustang.
This kernel does boot and run, and PCI does seem to work. As noted, the NIC is not yet correct but this is being investigated and may be firmware related, not kernel.
Al Stone (3): Fix arm64 compilation error in PNP code clocksource: arm_arch_timer: fix system hang arm64/pci/acpi: initial support for ACPI probing of PCI
Graeme Gregory (2): acpi: add arm to the platforms that use ioremap tty: SBSA compatible UART
Hanjun Guo (1): ARM64 / ACPI: Introduce some PCI functions when PCI is enabled
Kyle McMartin (1): arm64: don't set READ_IMPLIES_EXEC for EM_AARCH64 ELF objects
Mark Salter (17): ahci_xgene: add errata workaround for ATA_CMD_SMART arm64: use EFI as last resort for reboot and poweroff acpi: fix acpi_os_ioremap for arm64 arm64: add parking protocol support sata/xgene: support acpi probing xgene: add support for ACPI-probed serial port Revert "ahci_xgene: Skip the PHY and clock initialization if already configured by the firmware." arm64: add sev to parking protocol arm64: avoid need for console= to enable serial console xgene acpi network - first cut acpi: add utility to test for device dma coherency arm64: [NOT FOR UPSTREAM] fix dma_ops for ACPI and PCI devices arm64/pci: replace weak raw pci ops with settable ops arm64/acpi/pci: add support for parsing MCFG table arm64/acpi/pci: provide hook for MCFG fixups PCI: xgene: Provide fixup for ACPI MCFG support iommu/arm-smmu: fix NULL dereference with ACPI PCI devices
Mika Westerberg (2): ACPI: Add support for device specific properties ACPI: Allow drivers to match using Device Tree compatible property
Rafael J. Wysocki (2): Driver core: Unified device properties interface for platform firmware Driver core: Unified interface for firmware node properties
arch/arm64/Kconfig | 6 + arch/arm64/Makefile | 1 + arch/arm64/include/asm/acpi.h | 3 + arch/arm64/include/asm/elf.h | 3 +- arch/arm64/include/asm/pci.h | 57 +++ arch/arm64/include/asm/smp.h | 5 + arch/arm64/kernel/Makefile | 3 +- arch/arm64/kernel/acpi.c | 54 +- arch/arm64/kernel/cpu_ops.c | 4 + arch/arm64/kernel/efi.c | 11 + arch/arm64/kernel/pci.c | 108 ++-- arch/arm64/kernel/process.c | 6 + arch/arm64/kernel/setup.c | 22 + arch/arm64/kernel/smp_parking_protocol.c | 110 ++++ arch/arm64/mm/dma-mapping.c | 103 ++++ arch/arm64/pci/Makefile | 2 + arch/arm64/pci/mmconfig.c | 431 ++++++++++++++++ arch/arm64/pci/pci.c | 375 ++++++++++++++ drivers/acpi/Makefile | 1 + drivers/acpi/internal.h | 6 + drivers/acpi/osl.c | 6 +- drivers/acpi/property.c | 567 ++++++++++++++++++++ drivers/acpi/scan.c | 120 ++++- drivers/acpi/utils.c | 26 + drivers/ata/ahci_xgene.c | 30 +- drivers/base/Makefile | 2 +- drivers/base/property.c | 625 +++++++++++++++++++++++ drivers/clocksource/arm_arch_timer.c | 9 +- drivers/iommu/arm-smmu.c | 8 +- drivers/irqchip/irq-gic-v3.c | 10 + drivers/irqchip/irq-gic.c | 10 + drivers/net/ethernet/apm/xgene/xgene_enet_hw.c | 77 ++- drivers/net/ethernet/apm/xgene/xgene_enet_main.c | 68 ++- drivers/net/ethernet/apm/xgene/xgene_enet_main.h | 1 + drivers/of/base.c | 102 +++- drivers/pci/host/pci-xgene.c | 144 ++++++ drivers/pnp/resource.c | 2 + drivers/tty/Kconfig | 6 + drivers/tty/Makefile | 1 + drivers/tty/sbsauart.c | 355 +++++++++++++ drivers/tty/serial/8250/8250_dw.c | 9 + include/acpi/acpi_bus.h | 27 + include/acpi/acpi_io.h | 6 + include/asm-generic/vmlinux.lds.h | 7 + include/linux/acpi.h | 106 +++- include/linux/irqchip/arm-gic.h | 2 + include/linux/of.h | 44 ++ include/linux/property.h | 107 ++++ 48 files changed, 3670 insertions(+), 118 deletions(-) create mode 100644 arch/arm64/kernel/smp_parking_protocol.c create mode 100644 arch/arm64/pci/Makefile create mode 100644 arch/arm64/pci/mmconfig.c create mode 100644 arch/arm64/pci/pci.c create mode 100644 drivers/acpi/property.c create mode 100644 drivers/base/property.c create mode 100644 drivers/tty/sbsauart.c create mode 100644 include/linux/property.h
From: Kyle McMartin kmcmarti@redhat.com
Message-id: 20140513222526.GC26038@redacted.bos.redhat.com Patchwork-id: 79789 O-Subject: [ACADIA PATCH] arm64: don't set READ_IMPLIES_EXEC for EM_AARCH64 ELF objects Bugzilla: 1085528
BZ: https://bugzilla.redhat.com/show_bug.cgi?id=1085528 Upstream: submitted soon
[Sadly this isn't (yet) sufficient... but it fixes at least one issue here... cat /proc/$$/personality shows READ_IMPLIES_EXEC before. I'll try to figure the rest out tomorrow.]
Currently, we're accidentally ending up with executable stacks on AArch64 when the ABI says we shouldn't be, and relying on glibc to fix things up for us when we're loaded. However, SELinux will deny us mucking with the stack, and hit us with execmem AVCs.
The reason this is happening is somewhat complex:
fs/binfmt_elf.c:load_elf_binary() - initializes executable_stack = EXSTACK_DEFAULT implying the architecture should make up its mind. - does a pile of loading goo - runs through the program headers, looking for PT_GNU_STACK and setting (or unsetting) executable_stack if it finds it.
This is our first problem, we won't generate these unless an executable stack is explicitly requested.
- more ELF loading goo - sets whether we're a compat task or not (TIF_32BIT) based on compat.h - for compat reasons (pre-GNU_STACK) checks if the READ_IMPLIES_EXEC flag should be set for ancient toolchains
Here's our second problem, we test if read_implies_exec based on stk != EXSTACK_DISABLE_X, which is true since stk == EXSTACK_DEFAULT.
So we set current->personality |= READ_IMPLIES_EXEC like a broken legacy toolchain would want.
- Now we call setup_arg_pages to set up the stack...
fs/exec.c:setup_arg_pages() - lots of magic happens here - vm_flags gets initialized to VM_STACK_FLAGS
Here's our third problem, VM_STACK_FLAGS on arm64 is VM_DEFAULT_DATA_FLAG which tests READ_IMPLIES_EXEC and sets VM_EXEC if it's true. So we end up with an executable stack mapping, since we don't have executable_stack set (it's still EXSTACK_DEFAULT at this point) to unset it anywhere.
Bang. execstack AVC when the program starts running.
The easiest way I can see to fix this is to test if we're a legacy task and fix it up there. But that's not as simple as it sounds, because the 32-bit ABI depends on what revision of the CPU we've enabled (not that it matters since we're ARMv8...) Regardless, in the compat case, set READ_IMPLIES_EXEC if we've found a GNU_STACK header which explicitly requested it as in arch/arm/kernel/elf.c:arm_elf_read_implies_exec().
Signed-off-by: Kyle McMartin kmcmarti@redhat.com Signed-off-by: Donald Dutile ddutile@redhat.com --- arch/arm64/include/asm/elf.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/include/asm/elf.h b/arch/arm64/include/asm/elf.h index 1f65be3..c0f89a0 100644 --- a/arch/arm64/include/asm/elf.h +++ b/arch/arm64/include/asm/elf.h @@ -114,7 +114,8 @@ typedef struct user_fpsimd_state elf_fpregset_t; */ #define elf_check_arch(x) ((x)->e_machine == EM_AARCH64)
-#define elf_read_implies_exec(ex,stk) (stk != EXSTACK_DISABLE_X) +#define elf_read_implies_exec(ex,stk) (test_thread_flag(TIF_32BIT) \ + ? (stk == EXSTACK_ENABLE_X) : 0)
#define CORE_DUMP_USE_REGSET #define ELF_EXEC_PAGESIZE PAGE_SIZE
From: Mark Salter msalter@redhat.com
commit 2a0bdff6b958d1b2:
ahci_xgene: fix the dma state machine lockup for the IDENTIFY DEVICE PIO mode command.
added a workaround for X-Gene AHCI controller errata. This was done for all ATA_CMD_ID_ATA commands. The errata also appears to affect ATA_CMD_SMART commands as well. This was discovered when running smartd or just smartctl -x. This patch adds a dma engine restart for ATA_CMD_SMART commands which clears up the issues seen with smartd.
Signed-off-by: Mark Salter msalter@redhat.com --- drivers/ata/ahci_xgene.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/ata/ahci_xgene.c b/drivers/ata/ahci_xgene.c index 0f8538f..062cd2a 100644 --- a/drivers/ata/ahci_xgene.c +++ b/drivers/ata/ahci_xgene.c @@ -137,7 +137,8 @@ static unsigned int xgene_ahci_qc_issue(struct ata_queued_cmd *qc) struct xgene_ahci_context *ctx = hpriv->plat_data; int rc = 0;
- if (unlikely(ctx->last_cmd[ap->port_no] == ATA_CMD_ID_ATA)) + if (unlikely(ctx->last_cmd[ap->port_no] == ATA_CMD_ID_ATA || + ctx->last_cmd[ap->port_no] == ATA_CMD_SMART)) xgene_ahci_restart_engine(ap);
rc = ahci_qc_issue(qc);
From: Mark Salter msalter@redhat.com
Wire in support for EFI reboot and poweroff functions. We use these only if no other mechanism has been registered with arm_pm_reboot and/or pm_power_off respectively.
Signed-off-by: Mark Salter msalter@redhat.com --- arch/arm64/kernel/efi.c | 11 +++++++++++ arch/arm64/kernel/process.c | 6 ++++++ 2 files changed, 17 insertions(+)
diff --git a/arch/arm64/kernel/efi.c b/arch/arm64/kernel/efi.c index 95c49eb..a73daae 100644 --- a/arch/arm64/kernel/efi.c +++ b/arch/arm64/kernel/efi.c @@ -471,3 +471,14 @@ err_unmap: return -1; } early_initcall(arm64_enter_virtual_mode); + +/* + * If nothing else is handling pm_power_off, use EFI + * + * This is called from a late_initcall after other mechanisms + * have had a chance to register a handler. + */ +bool efi_poweroff_required(void) +{ + return pm_power_off == NULL; +} diff --git a/arch/arm64/kernel/process.c b/arch/arm64/kernel/process.c index fde9923..466f8cd 100644 --- a/arch/arm64/kernel/process.c +++ b/arch/arm64/kernel/process.c @@ -43,6 +43,7 @@ #include <linux/hw_breakpoint.h> #include <linux/personality.h> #include <linux/notifier.h> +#include <linux/efi.h>
#include <asm/compat.h> #include <asm/cacheflush.h> @@ -157,6 +158,11 @@ void machine_restart(char *cmd) do_kernel_restart(cmd);
/* + * If all else fails, try EFI + */ + efi_reboot(reboot_mode, cmd); + + /* * Whoops - the architecture was unable to reboot. */ printk("Reboot failed -- System halted\n");
From: Graeme Gregory graeme.gregory@linaro.org
Now with the base changes to the arm memory mapping it is safe to convert to using ioremap to map in the tables.
Signed-off-by: Al Stone al.stone@linaro.org Signed-off-by: Graeme Gregory graeme.gregory@linaro.org --- drivers/acpi/osl.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index 9964f70..5c480d5 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -336,11 +336,11 @@ acpi_map_lookup_virt(void __iomem *virt, acpi_size size) return NULL; }
-#ifndef CONFIG_IA64 -#define should_use_kmap(pfn) page_is_ram(pfn) -#else +#if defined(CONFIG_IA64) || defined(CONFIG_ARM) || defined(CONFIG_ARM64) /* ioremap will take care of cache attributes */ #define should_use_kmap(pfn) 0 +#else +#define should_use_kmap(pfn) page_is_ram(pfn) #endif
static void __iomem *acpi_map(acpi_physical_address pg_off, unsigned long pg_sz)
From: Mark Salter msalter@redhat.com
The acpi_os_ioremap() function may be used to map normal RAM or IO regions. The current implementation simply uses ioremap_cache(). This will work for some architectures, but arm64 ioremap_cache() cannot be used to map IO regions which don't support caching. So for arm64, use ioremap() for non-RAM regions.
Signed-off-by: Mark Salter msalter@redhat.com --- include/acpi/acpi_io.h | 6 ++++++ 1 file changed, 6 insertions(+)
diff --git a/include/acpi/acpi_io.h b/include/acpi/acpi_io.h index 444671e..9d573db 100644 --- a/include/acpi/acpi_io.h +++ b/include/acpi/acpi_io.h @@ -1,11 +1,17 @@ #ifndef _ACPI_IO_H_ #define _ACPI_IO_H_
+#include <linux/mm.h> #include <linux/io.h>
static inline void __iomem *acpi_os_ioremap(acpi_physical_address phys, acpi_size size) { +#ifdef CONFIG_ARM64 + if (!page_is_ram(phys >> PAGE_SHIFT)) + return ioremap(phys, size); +#endif + return ioremap_cache(phys, size); }
From: Graeme Gregory graeme.gregory@linaro.org
This is a subset of pl011 UART which does not supprt DMA or baud rate changing. It does, however, provide earlycon support (i.e., using "earlycon=ttySBSA" on the kernel command line).
It is specified in the Server Base System Architecture document from ARM.
Signed-off-by: Graeme Gregory graeme.gregory@linaro.org --- drivers/tty/Kconfig | 6 + drivers/tty/Makefile | 1 + drivers/tty/sbsauart.c | 355 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 362 insertions(+) create mode 100644 drivers/tty/sbsauart.c
diff --git a/drivers/tty/Kconfig b/drivers/tty/Kconfig index b24aa01..50fe279 100644 --- a/drivers/tty/Kconfig +++ b/drivers/tty/Kconfig @@ -419,4 +419,10 @@ config DA_CONSOLE help This enables a console on a Dash channel.
+config SBSAUART_TTY + tristate "SBSA UART TTY Driver" + help + Console and system TTY driver for the SBSA UART which is defined + in the Server Base System Architecure document for ARM64 servers. + endif # TTY diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile index 58ad1c0..c3211c0 100644 --- a/drivers/tty/Makefile +++ b/drivers/tty/Makefile @@ -29,5 +29,6 @@ obj-$(CONFIG_SYNCLINK) += synclink.o obj-$(CONFIG_PPC_EPAPR_HV_BYTECHAN) += ehv_bytechan.o obj-$(CONFIG_GOLDFISH_TTY) += goldfish.o obj-$(CONFIG_DA_TTY) += metag_da.o +obj-$(CONFIG_SBSAUART_TTY) += sbsauart.o
obj-y += ipwireless/ diff --git a/drivers/tty/sbsauart.c b/drivers/tty/sbsauart.c new file mode 100644 index 0000000..402f168 --- /dev/null +++ b/drivers/tty/sbsauart.c @@ -0,0 +1,355 @@ +/* + * SBSA (Server Base System Architecture) Compatible UART driver + * + * Copyright (C) 2014 Linaro Ltd + * + * Author: Graeme Gregory graeme.gregory@linaro.org + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/acpi.h> +#include <linux/amba/serial.h> +#include <linux/console.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/serial_core.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> + +struct sbsa_tty { + struct tty_port port; + spinlock_t lock; + void __iomem *base; + u32 irq; + int opencount; + struct console console; +}; + +static struct tty_driver *sbsa_tty_driver; +static struct sbsa_tty *sbsa_tty; + +#define SBSAUART_CHAR_MASK 0xFF + +static void sbsa_raw_putc(struct uart_port *port, int c) +{ + while (readw(port->membase + UART01x_FR) & UART01x_FR_TXFF) + ; + writew(c & 0xFF, port->membase + UART01x_DR); +} + +static void sbsa_uart_early_write(struct console *con, const char *buf, + unsigned count) +{ + struct earlycon_device *dev = con->data; + + uart_console_write(&dev->port, buf, count, sbsa_raw_putc); +} + +static int __init sbsa_uart_early_console_setup(struct earlycon_device *device, + const char *opt) +{ + if (!device->port.membase) + return -ENODEV; + + device->con->write = sbsa_uart_early_write; + return 0; +} +EARLYCON_DECLARE(sbsauart, sbsa_uart_early_console_setup); + +static void sbsa_tty_do_write(const char *buf, unsigned count) +{ + unsigned long irq_flags; + struct sbsa_tty *qtty = sbsa_tty; + void __iomem *base = qtty->base; + unsigned n; + + spin_lock_irqsave(&qtty->lock, irq_flags); + for (n = 0; n < count; n++) { + while (readw(base + UART01x_FR) & UART01x_FR_TXFF) + ; + writew(buf[n], base + UART01x_DR); + } + spin_unlock_irqrestore(&qtty->lock, irq_flags); +} + +static void sbsauart_fifo_to_tty(struct sbsa_tty *qtty) +{ + void __iomem *base = qtty->base; + unsigned int flag, max_count = 32; + u16 status, ch; + + while (max_count--) { + status = readw(base + UART01x_FR); + if (status & UART01x_FR_RXFE) + break; + + /* Take chars from the FIFO and update status */ + ch = readw(base + UART01x_DR); + flag = TTY_NORMAL; + + if (ch & UART011_DR_BE) + flag = TTY_BREAK; + else if (ch & UART011_DR_PE) + flag = TTY_PARITY; + else if (ch & UART011_DR_FE) + flag = TTY_FRAME; + else if (ch & UART011_DR_OE) + flag = TTY_OVERRUN; + + ch &= SBSAUART_CHAR_MASK; + + tty_insert_flip_char(&qtty->port, ch, flag); + } + + tty_schedule_flip(&qtty->port); + + /* Clear the RX IRQ */ + writew(UART011_RXIC | UART011_RXIC, base + UART011_ICR); +} + +static irqreturn_t sbsa_tty_interrupt(int irq, void *dev_id) +{ + struct sbsa_tty *qtty = sbsa_tty; + unsigned long irq_flags; + + spin_lock_irqsave(&qtty->lock, irq_flags); + sbsauart_fifo_to_tty(qtty); + spin_unlock_irqrestore(&qtty->lock, irq_flags); + + return IRQ_HANDLED; +} + +static int sbsa_tty_open(struct tty_struct *tty, struct file *filp) +{ + struct sbsa_tty *qtty = sbsa_tty; + + return tty_port_open(&qtty->port, tty, filp); +} + +static void sbsa_tty_close(struct tty_struct *tty, struct file *filp) +{ + tty_port_close(tty->port, tty, filp); +} + +static void sbsa_tty_hangup(struct tty_struct *tty) +{ + tty_port_hangup(tty->port); +} + +static int sbsa_tty_write(struct tty_struct *tty, const unsigned char *buf, + int count) +{ + sbsa_tty_do_write(buf, count); + return count; +} + +static int sbsa_tty_write_room(struct tty_struct *tty) +{ + return 32; +} + +static void sbsa_tty_console_write(struct console *co, const char *b, + unsigned count) +{ + sbsa_tty_do_write(b, count); + + if (b[count - 1] == '\n') + sbsa_tty_do_write("\r", 1); +} + +static struct tty_driver *sbsa_tty_console_device(struct console *c, + int *index) +{ + *index = c->index; + return sbsa_tty_driver; +} + +static int sbsa_tty_console_setup(struct console *co, char *options) +{ + if ((unsigned)co->index > 0) + return -ENODEV; + if (sbsa_tty->base == NULL) + return -ENODEV; + return 0; +} + +static struct tty_port_operations sbsa_port_ops = { +}; + +static const struct tty_operations sbsa_tty_ops = { + .open = sbsa_tty_open, + .close = sbsa_tty_close, + .hangup = sbsa_tty_hangup, + .write = sbsa_tty_write, + .write_room = sbsa_tty_write_room, +}; + +static int sbsa_tty_create_driver(void) +{ + int ret; + struct tty_driver *tty; + + sbsa_tty = kzalloc(sizeof(*sbsa_tty), GFP_KERNEL); + if (sbsa_tty == NULL) { + ret = -ENOMEM; + goto err_alloc_sbsa_tty_failed; + } + tty = alloc_tty_driver(1); + if (tty == NULL) { + ret = -ENOMEM; + goto err_alloc_tty_driver_failed; + } + tty->driver_name = "sbsauart"; + tty->name = "ttySBSA"; + tty->type = TTY_DRIVER_TYPE_SERIAL; + tty->subtype = SERIAL_TYPE_NORMAL; + tty->init_termios = tty_std_termios; + tty->flags = TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW | + TTY_DRIVER_DYNAMIC_DEV; + tty_set_operations(tty, &sbsa_tty_ops); + ret = tty_register_driver(tty); + if (ret) + goto err_tty_register_driver_failed; + + sbsa_tty_driver = tty; + return 0; + +err_tty_register_driver_failed: + put_tty_driver(tty); +err_alloc_tty_driver_failed: + kfree(sbsa_tty); + sbsa_tty = NULL; +err_alloc_sbsa_tty_failed: + return ret; +} + +static void sbsa_tty_delete_driver(void) +{ + tty_unregister_driver(sbsa_tty_driver); + put_tty_driver(sbsa_tty_driver); + sbsa_tty_driver = NULL; + kfree(sbsa_tty); + sbsa_tty = NULL; +} + +static int sbsa_tty_probe(struct platform_device *pdev) +{ + struct sbsa_tty *qtty; + int ret = -EINVAL; + int i; + struct resource *r; + struct device *ttydev; + void __iomem *base; + u32 irq; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (r == NULL) + return -EINVAL; + + base = ioremap(r->start, r->end - r->start); + if (base == NULL) + pr_err("sbsa_tty: unable to remap base\n"); + + r = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (r == NULL) + goto err_unmap; + + irq = r->start; + + if (pdev->id > 0) + goto err_unmap; + + ret = sbsa_tty_create_driver(); + if (ret) + goto err_unmap; + + qtty = sbsa_tty; + spin_lock_init(&qtty->lock); + tty_port_init(&qtty->port); + qtty->port.ops = &sbsa_port_ops; + qtty->base = base; + qtty->irq = irq; + + /* Clear and Mask all IRQs */ + writew(0, base + UART011_IMSC); + writew(0xFFFF, base + UART011_ICR); + + ret = request_irq(irq, sbsa_tty_interrupt, IRQF_SHARED, + "sbsa_tty", pdev); + if (ret) + goto err_request_irq_failed; + + /* Unmask the RX IRQ */ + writew(UART011_RXIM | UART011_RTIM, base + UART011_IMSC); + + ttydev = tty_port_register_device(&qtty->port, sbsa_tty_driver, + 0, &pdev->dev); + if (IS_ERR(ttydev)) { + ret = PTR_ERR(ttydev); + goto err_tty_register_device_failed; + } + + strcpy(qtty->console.name, "ttySBSA"); + qtty->console.write = sbsa_tty_console_write; + qtty->console.device = sbsa_tty_console_device; + qtty->console.setup = sbsa_tty_console_setup; + qtty->console.flags = CON_PRINTBUFFER; + qtty->console.index = pdev->id; + register_console(&qtty->console); + + return 0; + + tty_unregister_device(sbsa_tty_driver, i); +err_tty_register_device_failed: + free_irq(irq, pdev); +err_request_irq_failed: + sbsa_tty_delete_driver(); +err_unmap: + iounmap(base); + return ret; +} + +static int sbsa_tty_remove(struct platform_device *pdev) +{ + struct sbsa_tty *qtty; + + qtty = sbsa_tty; + unregister_console(&qtty->console); + tty_unregister_device(sbsa_tty_driver, pdev->id); + iounmap(qtty->base); + qtty->base = 0; + free_irq(qtty->irq, pdev); + sbsa_tty_delete_driver(); + return 0; +} + +static const struct acpi_device_id sbsa_acpi_match[] = { + { "ARMH0011", 0 }, + { } +}; + +static struct platform_driver sbsa_tty_platform_driver = { + .probe = sbsa_tty_probe, + .remove = sbsa_tty_remove, + .driver = { + .name = "sbsa_tty", + .acpi_match_table = ACPI_PTR(sbsa_acpi_match), + } +}; + +module_platform_driver(sbsa_tty_platform_driver); + +MODULE_LICENSE("GPL v2");
From: Al Stone ahs3@redhat.com
Signed-off-by: Mark Salter msalter@redhat.com --- drivers/pnp/resource.c | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/drivers/pnp/resource.c b/drivers/pnp/resource.c index 782e822..d952462 100644 --- a/drivers/pnp/resource.c +++ b/drivers/pnp/resource.c @@ -313,6 +313,7 @@ static int pci_dev_uses_irq(struct pnp_dev *pnp, struct pci_dev *pci, progif = class & 0xff; class >>= 8;
+#ifdef HAVE_ARCH_PCI_GET_LEGACY_IDE_IRQ if (class == PCI_CLASS_STORAGE_IDE) { /* * Unless both channels are native-PCI mode only, @@ -326,6 +327,7 @@ static int pci_dev_uses_irq(struct pnp_dev *pnp, struct pci_dev *pci, return 1; } } +#endif /* HAVE_ARCH_PCI_GET_LEGACY_IDE_IRQ */
return 0; }
From: Hanjun Guo hanjun.guo@linaro.org
Introduce some PCI functions to make ACPI can be compiled when CONFIG_PCI is enabled, these functions should be revisited when implemented on ARM64.
Signed-off-by: Hanjun Guo hanjun.guo@linaro.org [fixed up for 3.17-rc] Signed-off-by: Mark Salter msalter@redhat.com --- arch/arm64/Makefile | 1 + arch/arm64/kernel/acpi.c | 20 ++++++++++++++++++++ arch/arm64/pci/Makefile | 1 + arch/arm64/pci/pci.c | 28 ++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+) create mode 100644 arch/arm64/pci/Makefile create mode 100644 arch/arm64/pci/pci.c
diff --git a/arch/arm64/Makefile b/arch/arm64/Makefile index 20901ff..983d72a 100644 --- a/arch/arm64/Makefile +++ b/arch/arm64/Makefile @@ -49,6 +49,7 @@ core-$(CONFIG_NET) += arch/arm64/net/ core-$(CONFIG_KVM) += arch/arm64/kvm/ core-$(CONFIG_XEN) += arch/arm64/xen/ core-$(CONFIG_CRYPTO) += arch/arm64/crypto/ +drivers-$(CONFIG_PCI) += arch/arm64/pci/ libs-y := arch/arm64/lib/ $(libs-y) libs-y += $(LIBGCC) libs-$(CONFIG_EFI_STUB) += drivers/firmware/efi/libstub/ diff --git a/arch/arm64/kernel/acpi.c b/arch/arm64/kernel/acpi.c index 41312bd..6411600 100644 --- a/arch/arm64/kernel/acpi.c +++ b/arch/arm64/kernel/acpi.c @@ -357,3 +357,23 @@ static int __init parse_acpi(char *arg) return 0; } early_param("acpi", parse_acpi); + +int acpi_isa_irq_to_gsi(unsigned isa_irq, u32 *gsi) +{ + return -1; +} + +int acpi_register_ioapic(acpi_handle handle, u64 phys_addr, u32 gsi_base) +{ + /* TBD */ + return -EINVAL; +} +EXPORT_SYMBOL(acpi_register_ioapic); + +int acpi_unregister_ioapic(acpi_handle handle, u32 gsi_base) +{ + /* TBD */ + return -EINVAL; +} +EXPORT_SYMBOL(acpi_unregister_ioapic); + diff --git a/arch/arm64/pci/Makefile b/arch/arm64/pci/Makefile new file mode 100644 index 0000000..b8d5dbd --- /dev/null +++ b/arch/arm64/pci/Makefile @@ -0,0 +1 @@ +obj-y += pci.o diff --git a/arch/arm64/pci/pci.c b/arch/arm64/pci/pci.c new file mode 100644 index 0000000..b03b0eb --- /dev/null +++ b/arch/arm64/pci/pci.c @@ -0,0 +1,28 @@ +#include <linux/acpi.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/pci.h> + +/** + * raw_pci_read - Platform-specific PCI config space access. + * + * Default empty implementation. Replace with an architecture-specific setup + * routine, if necessary. + */ +int __weak raw_pci_read(unsigned int domain, unsigned int bus, + unsigned int devfn, int reg, int len, u32 *val) +{ + return -EINVAL; +} + +int __weak raw_pci_write(unsigned int domain, unsigned int bus, + unsigned int devfn, int reg, int len, u32 val) +{ + return -EINVAL; +} + +/* Root bridge scanning */ +struct pci_bus *pci_acpi_scan_root(struct acpi_pci_root *root) +{ + return NULL; +}
From: Mark Salter msalter@redhat.com
This is a first-cut effort at parking protocol support. It is very much a work in progress (as is the spec it is based on). This code deviates from the current spec in a number of ways to work around current firmware issues and issues with kernels using 64K page sizes.
caveat utilitor
Signed-off-by: Mark Salter msalter@redhat.com --- arch/arm64/Kconfig | 3 + arch/arm64/include/asm/acpi.h | 3 + arch/arm64/include/asm/smp.h | 5 ++ arch/arm64/kernel/Makefile | 3 +- arch/arm64/kernel/acpi.c | 34 ++++++++-- arch/arm64/kernel/cpu_ops.c | 4 ++ arch/arm64/kernel/smp_parking_protocol.c | 107 +++++++++++++++++++++++++++++++ drivers/irqchip/irq-gic-v3.c | 10 +++ drivers/irqchip/irq-gic.c | 10 +++ include/linux/irqchip/arm-gic.h | 2 + 10 files changed, 174 insertions(+), 7 deletions(-) create mode 100644 arch/arm64/kernel/smp_parking_protocol.c
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index aee6a60..7789db7 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -269,6 +269,9 @@ config SMP
If you don't know what to do here, say N.
+config ARM_PARKING_PROTOCOL + def_bool y if SMP + config SCHED_MC bool "Multi-core scheduler support" depends on SMP diff --git a/arch/arm64/include/asm/acpi.h b/arch/arm64/include/asm/acpi.h index 483ff45..6e692f4 100644 --- a/arch/arm64/include/asm/acpi.h +++ b/arch/arm64/include/asm/acpi.h @@ -89,11 +89,14 @@ static inline bool acpi_has_cpu_in_madt(void) static inline void arch_fix_phys_package_id(int num, u32 slot) { } void __init acpi_smp_init_cpus(void);
+extern int acpi_get_cpu_parked_address(int cpu, u64 *addr); + #else static inline void disable_acpi(void) { } static inline bool acpi_psci_present(void) { return false; } static inline bool acpi_psci_use_hvc(void) { return false; } static inline void acpi_smp_init_cpus(void) { } +static inline int acpi_get_cpu_parked_address(int cpu, u64 *addr) { return -EOPNOTSUPP; } #endif /* CONFIG_ACPI */
#endif /*_ASM_ACPI_H*/ diff --git a/arch/arm64/include/asm/smp.h b/arch/arm64/include/asm/smp.h index bf22650..3411561 100644 --- a/arch/arm64/include/asm/smp.h +++ b/arch/arm64/include/asm/smp.h @@ -52,6 +52,11 @@ extern void set_smp_cross_call(void (*)(const struct cpumask *, unsigned int)); extern void (*__smp_cross_call)(const struct cpumask *, unsigned int);
/* + * Provide a function to signal a parked secondary CPU. + */ +extern void set_smp_boot_wakeup_call(void (*)(int cpu)); + +/* * Called from the secondary holding pen, this is the secondary CPU entry point. */ asmlinkage void secondary_start_kernel(void); diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile index f48e3f7..f4ba4fe 100644 --- a/arch/arm64/kernel/Makefile +++ b/arch/arm64/kernel/Makefile @@ -21,7 +21,8 @@ arm64-obj-$(CONFIG_COMPAT) += sys32.o kuser32.o signal32.o \ sys_compat.o arm64-obj-$(CONFIG_FUNCTION_TRACER) += ftrace.o entry-ftrace.o arm64-obj-$(CONFIG_MODULES) += arm64ksyms.o module.o -arm64-obj-$(CONFIG_SMP) += smp.o smp_spin_table.o topology.o +arm64-obj-$(CONFIG_SMP) += smp.o smp_spin_table.o topology.o \ + smp_parking_protocol.o arm64-obj-$(CONFIG_PERF_EVENTS) += perf_regs.o arm64-obj-$(CONFIG_HW_PERF_EVENTS) += perf_event.o arm64-obj-$(CONFIG_HAVE_HW_BREAKPOINT) += hw_breakpoint.o diff --git a/arch/arm64/kernel/acpi.c b/arch/arm64/kernel/acpi.c index 6411600..7b3318b 100644 --- a/arch/arm64/kernel/acpi.c +++ b/arch/arm64/kernel/acpi.c @@ -37,6 +37,9 @@ EXPORT_SYMBOL(acpi_pci_disabled);
static int enabled_cpus; /* Processors (GICC) with enabled flag in MADT */
+static char *boot_method; +static u64 parked_address[NR_CPUS]; + /* * Since we're on ARM, the default interrupt routing model * clearly has to be GIC. @@ -71,7 +74,7 @@ void __init __acpi_unmap_table(char *map, unsigned long size) * * Returns the logical cpu number which maps to MPIDR */ -static int acpi_map_gic_cpu_interface(u64 mpidr, u8 enabled) +static int acpi_map_gic_cpu_interface(u64 mpidr, u64 parked_addr, u8 enabled) { int cpu;
@@ -125,9 +128,11 @@ static int acpi_map_gic_cpu_interface(u64 mpidr, u8 enabled) cpu = 0; }
+ parked_address[cpu] = parked_addr; + /* CPU 0 was already initialized */ if (cpu) { - cpu_ops[cpu] = cpu_get_ops(acpi_psci_present() ? "psci" : NULL); + cpu_ops[cpu] = cpu_get_ops(boot_method); if (!cpu_ops[cpu]) return -EINVAL;
@@ -140,7 +145,7 @@ static int acpi_map_gic_cpu_interface(u64 mpidr, u8 enabled) set_cpu_possible(cpu, true); } else { /* get cpu0's ops, no need to return if ops is null */ - cpu_ops[0] = cpu_get_ops(acpi_psci_present() ? "psci" : NULL); + cpu_ops[0] = cpu_get_ops(boot_method); }
enabled_cpus++; @@ -161,7 +166,7 @@ acpi_parse_gic_cpu_interface(struct acpi_subtable_header *header, acpi_table_print_madt_entry(header);
acpi_map_gic_cpu_interface(processor->arm_mpidr & MPIDR_HWID_BITMASK, - processor->flags & ACPI_MADT_ENABLED); + processor->parked_address, processor->flags & ACPI_MADT_ENABLED);
return 0; } @@ -278,9 +283,12 @@ static int __init acpi_parse_fadt(struct acpi_table_header *table) * the ACPI spec or the Parking protocol spec. */ if (acpi_psci_present()) - return 0; + boot_method = "psci"; + else if (IS_ENABLED(CONFIG_ARM_PARKING_PROTOCOL)) + boot_method = "parking-protocol";
- pr_warn("No PSCI support, will not bring up secondary CPUs\n"); + if (!boot_method) + pr_warn("No boot method, will not bring up secondary CPUs\n"); return -EOPNOTSUPP; }
@@ -341,6 +349,20 @@ void __init acpi_gic_init(void) early_acpi_os_unmap_memory((char *)table, tbl_size); }
+/* + * Parked Address in ACPI GIC structure will be used as the CPU + * release address + */ +int acpi_get_cpu_parked_address(int cpu, u64 *addr) +{ + if (!addr || !parked_address[cpu]) + return -EINVAL; + + *addr = parked_address[cpu]; + + return 0; +} + static int __init parse_acpi(char *arg) { if (!arg) diff --git a/arch/arm64/kernel/cpu_ops.c b/arch/arm64/kernel/cpu_ops.c index 1a04deb..1d90f31 100644 --- a/arch/arm64/kernel/cpu_ops.c +++ b/arch/arm64/kernel/cpu_ops.c @@ -23,6 +23,7 @@ #include <linux/string.h>
extern const struct cpu_operations smp_spin_table_ops; +extern const struct cpu_operations smp_parking_protocol_ops; extern const struct cpu_operations cpu_psci_ops;
const struct cpu_operations *cpu_ops[NR_CPUS]; @@ -30,6 +31,9 @@ const struct cpu_operations *cpu_ops[NR_CPUS]; static const struct cpu_operations *supported_cpu_ops[] = { #ifdef CONFIG_SMP &smp_spin_table_ops, +#ifdef CONFIG_ARM_PARKING_PROTOCOL + &smp_parking_protocol_ops, +#endif #endif &cpu_psci_ops, NULL, diff --git a/arch/arm64/kernel/smp_parking_protocol.c b/arch/arm64/kernel/smp_parking_protocol.c new file mode 100644 index 0000000..e9c0c68 --- /dev/null +++ b/arch/arm64/kernel/smp_parking_protocol.c @@ -0,0 +1,107 @@ +/* + * Parking Protocol SMP initialisation + * + * Based largely on spin-table method. + * + * Copyright (C) 2013 ARM Ltd. + * + * 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/delay.h> +#include <linux/init.h> +#include <linux/of.h> +#include <linux/smp.h> +#include <linux/types.h> +#include <linux/acpi.h> + +#include <asm/cacheflush.h> +#include <asm/cpu_ops.h> +#include <asm/cputype.h> +#include <asm/smp_plat.h> + +static phys_addr_t cpu_mailbox_addr[NR_CPUS]; + +static void (*__smp_boot_wakeup)(int cpu); + +void set_smp_boot_wakeup_call(void (*fn)(int cpu)) +{ + __smp_boot_wakeup = fn; +} + +static int smp_parking_protocol_cpu_init(struct device_node *dn, + unsigned int cpu) +{ + /* + * Determine the mailbox address. + */ + if (!acpi_get_cpu_parked_address(cpu, &cpu_mailbox_addr[cpu])) { + pr_info("%s: ACPI parked addr=%llx\n", + __func__, cpu_mailbox_addr[cpu]); + return 0; + } + + pr_err("CPU %d: missing or invalid parking protocol mailbox\n", cpu); + + return -1; +} + +static int smp_parking_protocol_cpu_prepare(unsigned int cpu) +{ + return 0; +} + +struct parking_protocol_mailbox { + __le32 cpu_id; + __le32 reserved; + __le64 entry_point; +}; + +static int smp_parking_protocol_cpu_boot(unsigned int cpu) +{ + struct parking_protocol_mailbox __iomem *mailbox; + + if (!cpu_mailbox_addr[cpu] || !__smp_boot_wakeup) + return -ENODEV; + + /* + * The mailbox may or may not be inside the linear mapping. + * As ioremap_cache will either give us a new mapping or reuse the + * existing linear mapping, we can use it to cover both cases. In + * either case the memory will be MT_NORMAL. + */ + mailbox = ioremap_cache(cpu_mailbox_addr[cpu], sizeof(*mailbox)); + if (!mailbox) + return -ENOMEM; + + /* + * We write the entry point and cpu id as LE regardless of the + * native endianess of the kernel. Therefore, any boot-loaders + * that read this address need to convert this address to the + * Boot-Loader's endianess before jumping. + */ + writeq(__pa(secondary_entry), &mailbox->entry_point); + writel(cpu, &mailbox->cpu_id); + __flush_dcache_area(mailbox, sizeof(*mailbox)); + __smp_boot_wakeup(cpu); + + iounmap(mailbox); + + return 0; +} + +const struct cpu_operations smp_parking_protocol_ops = { + .name = "parking-protocol", + .cpu_init = smp_parking_protocol_cpu_init, + .cpu_prepare = smp_parking_protocol_cpu_prepare, + .cpu_boot = smp_parking_protocol_cpu_boot, +}; diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index aa17ae8..d330dab 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c @@ -506,9 +506,19 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) isb(); }
+#ifdef CONFIG_ARM_PARKING_PROTOCOL +static void gic_wakeup_parked_cpu(int cpu) +{ + gic_raise_softirq(cpumask_of(cpu), 0); +} +#endif + static void gic_smp_init(void) { set_smp_cross_call(gic_raise_softirq); +#ifdef CONFIG_ARM_PARKING_PROTOCOL + set_smp_boot_wakeup_call(gic_wakeup_parked_cpu); +#endif register_cpu_notifier(&gic_cpu_notifier); }
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index afdeaa1..26e6773 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -643,6 +643,13 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq)
raw_spin_unlock_irqrestore(&irq_controller_lock, flags); } + +#ifdef CONFIG_ARM_PARKING_PROTOCOL +static void gic_wakeup_parked_cpu(int cpu) +{ + gic_raise_softirq(cpumask_of(cpu), GIC_DIST_SOFTINT_NSATT); +} +#endif #endif
#ifdef CONFIG_BL_SWITCHER @@ -998,6 +1005,9 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start, #ifdef CONFIG_SMP set_smp_cross_call(gic_raise_softirq); register_cpu_notifier(&gic_cpu_notifier); +#ifdef CONFIG_ARM_PARKING_PROTOCOL + set_smp_boot_wakeup_call(gic_wakeup_parked_cpu); +#endif #endif set_handle_irq(gic_handle_irq); } diff --git a/include/linux/irqchip/arm-gic.h b/include/linux/irqchip/arm-gic.h index 13eed92..dc9cb5f 100644 --- a/include/linux/irqchip/arm-gic.h +++ b/include/linux/irqchip/arm-gic.h @@ -55,6 +55,8 @@ (GICD_INT_DEF_PRI << 8) |\ GICD_INT_DEF_PRI)
+#define GIC_DIST_SOFTINT_NSATT 0x8000 + #define GICH_HCR 0x0 #define GICH_VTR 0x4 #define GICH_VMCR 0x8
On Monday 10 November 2014 19:46:56 al.stone@linaro.org wrote:
From: Mark Salter msalter@redhat.com
This is a first-cut effort at parking protocol support. It is very much a work in progress (as is the spec it is based on). This code deviates from the current spec in a number of ways to work around current firmware issues and issues with kernels using 64K page sizes.
caveat utilitor
Signed-off-by: Mark Salter msalter@redhat.com
I think we should avoid this one and only allow PSCI. Otherwise there is a danger of other platforms implementing only the parking protocol, as you mentioned the current AMD firmware does.
Arnd
From: Mark Salter msalter@redhat.com
Signed-off-by: Mark Salter msalter@redhat.com --- drivers/ata/ahci_xgene.c | 12 ++++++++++++ 1 file changed, 12 insertions(+)
diff --git a/drivers/ata/ahci_xgene.c b/drivers/ata/ahci_xgene.c index 062cd2a..28fde00 100644 --- a/drivers/ata/ahci_xgene.c +++ b/drivers/ata/ahci_xgene.c @@ -28,6 +28,7 @@ #include <linux/of_address.h> #include <linux/of_irq.h> #include <linux/phy/phy.h> +#include <linux/acpi.h> #include "ahci.h"
/* Max # of disk per a controller */ @@ -534,6 +535,16 @@ disable_resources: return rc; }
+#ifdef CONFIG_ACPI +static const struct acpi_device_id xgene_ahci_acpi_match[] = { + { "APMC0D00", }, + { "APMC0D0D", }, + { "APMC0D09", }, + { } +}; +MODULE_DEVICE_TABLE(acpi, xgene_ahci_acpi_match); +#endif + static const struct of_device_id xgene_ahci_of_match[] = { {.compatible = "apm,xgene-ahci"}, {}, @@ -547,6 +558,7 @@ static struct platform_driver xgene_ahci_driver = { .name = "xgene-ahci", .owner = THIS_MODULE, .of_match_table = xgene_ahci_of_match, + .acpi_match_table = ACPI_PTR(xgene_ahci_acpi_match), }, };
On Monday 10 November 2014 19:46:57 al.stone@linaro.org wrote:
return rc;
} +#ifdef CONFIG_ACPI +static const struct acpi_device_id xgene_ahci_acpi_match[] = {
{ "APMC0D00", },
{ "APMC0D0D", },
{ "APMC0D09", },
{ }
+}; +MODULE_DEVICE_TABLE(acpi, xgene_ahci_acpi_match); +#endif
static const struct of_device_id xgene_ahci_of_match[] = { {.compatible = "apm,xgene-ahci"}, {},
I don't think we should do this, since even level 0 of SBSA doesn't allow random SATA controllers, but requires AHCI-1.3 or later.
Arnd
From: Mark Salter msalter@redhat.com
--- drivers/tty/serial/8250/8250_dw.c | 9 +++++++++ 1 file changed, 9 insertions(+)
diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c index beea6ca..7038a2d 100644 --- a/drivers/tty/serial/8250/8250_dw.c +++ b/drivers/tty/serial/8250/8250_dw.c @@ -310,10 +310,18 @@ static int dw8250_probe_of(struct uart_port *p, static int dw8250_probe_acpi(struct uart_8250_port *up, struct dw8250_data *data) { + const struct acpi_device_id *id; struct uart_port *p = &up->port;
dw8250_setup_port(up);
+ id = acpi_match_device(p->dev->driver->acpi_match_table, p->dev); + if (!id) + return -ENODEV; + + if (!p->uartclk) + p->uartclk = (unsigned int)id->driver_data; + p->iotype = UPIO_MEM32; p->serial_in = dw8250_serial_in32; p->serial_out = dw8250_serial_out32; @@ -536,6 +544,7 @@ static const struct acpi_device_id dw8250_acpi_match[] = { { "INT3435", 0 }, { "80860F0A", 0 }, { "8086228A", 0 }, + { "APMC0D08", 50000000}, { }, }; MODULE_DEVICE_TABLE(acpi, dw8250_acpi_match);
On Monday 10 November 2014 19:46:58 al.stone@linaro.org wrote:
dw8250_setup_port(up);
id = acpi_match_device(p->dev->driver->acpi_match_table, p->dev);
if (!id)
return -ENODEV;
if (!p->uartclk)
p->uartclk = (unsigned int)id->driver_data;
This definitely need an explanation. Why can't you do it the way that Intel does?
Arnd
From: Mark Salter msalter@redhat.com
This reverts commit 0bed13bebd6c99d097796d2ca6c4f10fb5b2eabc.
Temporarily revert for backwards compatibility with rh-0.12-1 firmware --- drivers/ata/ahci_xgene.c | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-)
diff --git a/drivers/ata/ahci_xgene.c b/drivers/ata/ahci_xgene.c index 28fde00..2d8103a 100644 --- a/drivers/ata/ahci_xgene.c +++ b/drivers/ata/ahci_xgene.c @@ -150,14 +150,6 @@ static unsigned int xgene_ahci_qc_issue(struct ata_queued_cmd *qc) return rc; }
-static bool xgene_ahci_is_memram_inited(struct xgene_ahci_context *ctx) -{ - void __iomem *diagcsr = ctx->csr_diag; - - return (readl(diagcsr + CFG_MEM_RAM_SHUTDOWN) == 0 && - readl(diagcsr + BLOCK_MEM_RDY) == 0xFFFFFFFF); -} - /** * xgene_ahci_read_id - Read ID data from the specified device * @dev: device @@ -503,11 +495,6 @@ static int xgene_ahci_probe(struct platform_device *pdev) return -ENODEV; }
- if (xgene_ahci_is_memram_inited(ctx)) { - dev_info(dev, "skip clock and PHY initialization\n"); - goto skip_clk_phy; - } - /* Due to errata, HW requires full toggle transition */ rc = ahci_platform_enable_clks(hpriv); if (rc) @@ -520,7 +507,7 @@ static int xgene_ahci_probe(struct platform_device *pdev)
/* Configure the host controller */ xgene_ahci_hw_init(hpriv); -skip_clk_phy: + hpriv->flags = AHCI_HFLAG_NO_PMP | AHCI_HFLAG_NO_NCQ;
rc = ahci_platform_init_host(pdev, hpriv, &xgene_ahci_port_info);
From: Mark Salter msalter@redhat.com
Parking protocol wakes secondary cores with an interrupt. This patch adds an additional sev() to send an event. This is a temporary hack for APM Mustang board and not intended for upstream.
Signed-off-by: Mark Salter msalter@redhat.com --- arch/arm64/kernel/smp_parking_protocol.c | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/arch/arm64/kernel/smp_parking_protocol.c b/arch/arm64/kernel/smp_parking_protocol.c index e9c0c68..e1153ce 100644 --- a/arch/arm64/kernel/smp_parking_protocol.c +++ b/arch/arm64/kernel/smp_parking_protocol.c @@ -94,6 +94,9 @@ static int smp_parking_protocol_cpu_boot(unsigned int cpu) __flush_dcache_area(mailbox, sizeof(*mailbox)); __smp_boot_wakeup(cpu);
+ /* temp hack for broken firmware */ + sev(); + iounmap(mailbox);
return 0;
On Mon, Nov 10, 2014 at 07:47:00PM -0700, al.stone@linaro.org wrote:
From: Mark Salter msalter@redhat.com
Parking protocol wakes secondary cores with an interrupt. This patch adds an additional sev() to send an event. This is a temporary hack for APM Mustang board and not intended for upstream.
Signed-off-by: Mark Salter msalter@redhat.com
arch/arm64/kernel/smp_parking_protocol.c | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/arch/arm64/kernel/smp_parking_protocol.c b/arch/arm64/kernel/smp_parking_protocol.c index e9c0c68..e1153ce 100644 --- a/arch/arm64/kernel/smp_parking_protocol.c +++ b/arch/arm64/kernel/smp_parking_protocol.c @@ -94,6 +94,9 @@ static int smp_parking_protocol_cpu_boot(unsigned int cpu) __flush_dcache_area(mailbox, sizeof(*mailbox)); __smp_boot_wakeup(cpu);
- /* temp hack for broken firmware */
- sev();
- iounmap(mailbox);
return 0;
Will this break seattle?
Graeme
From: Mark Salter msalter@redhat.com
Tell kernel to prefer one of the serial ports on platforms pl011, 8250, or sbsa uarts. console= on command line will override these assumed preferences.
Signed-off-by: Mark Salter msalter@redhat.com --- arch/arm64/kernel/setup.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+)
diff --git a/arch/arm64/kernel/setup.c b/arch/arm64/kernel/setup.c index 070d99a..335adb1 100644 --- a/arch/arm64/kernel/setup.c +++ b/arch/arm64/kernel/setup.c @@ -519,3 +519,25 @@ const struct seq_operations cpuinfo_op = { .stop = c_stop, .show = c_show }; + +/* + * Temporary hack to avoid need for console= on command line + */ +static int __init arm64_console_setup(void) +{ + /* Allow cmdline to override our assumed preferences */ + if (console_set_on_cmdline) + return 0; + + if (IS_ENABLED(CONFIG_SBSAUART_TTY)) + add_preferred_console("ttySBSA", 0, "115200"); + + if (IS_ENABLED(CONFIG_SERIAL_AMBA_PL011)) + add_preferred_console("ttyAMA", 0, "115200"); + + if (IS_ENABLED(CONFIG_SERIAL_8250)) + add_preferred_console("ttyS", 0, "115200"); + + return 0; +} +early_initcall(arm64_console_setup);
From: Mika Westerberg mika.westerberg@linux.intel.com
Device Tree is used in many embedded systems to describe the system configuration to the OS. It supports attaching properties or name-value pairs to the devices it describe. With these properties one can pass additional information to the drivers that would not be available otherwise.
ACPI is another configuration mechanism (among other things) typically seen, but not limited to, x86 machines. ACPI allows passing arbitrary data from methods but there has not been mechanism equivalent to Device Tree until the introduction of _DSD in the recent publication of the ACPI 5.1 specification.
In order to facilitate ACPI usage in systems where Device Tree is typically used, it would be beneficial to standardize a way to retrieve Device Tree style properties from ACPI devices, which is what we do in this patch.
If a given device described in ACPI namespace wants to export properties it must implement _DSD method (Device Specific Data, introduced with ACPI 5.1) that returns the properties in a package of packages. For example:
Name (_DSD, Package () { ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package () {"name1", <VALUE1>}, Package () {"name2", <VALUE2>}, ... } })
The UUID reserved for properties is daffd814-6eba-4d8c-8a91-bc9bbf4aa301 and is documented in the ACPI 5.1 companion document called "_DSD Implementation Guide" [1], [2].
We add several helper functions that can be used to extract these properties and convert them to different Linux data types.
The ultimate goal is that we only have one device property API that retrieves the requested properties from Device Tree or from ACPI transparent to the caller.
[1] http://www.uefi.org/sites/default/files/resources/_DSD-implementation-guide-... [2] http://www.uefi.org/sites/default/files/resources/_DSD-device-properties-UUI...
Reviewed-by: Hanjun Guo hanjun.guo@linaro.org Reviewed-by: Josh Triplett josh@joshtriplett.org Reviewed-by: Grant Likely grant.likely@linaro.org Signed-off-by: Darren Hart dvhart@linux.intel.com Signed-off-by: Mika Westerberg mika.westerberg@linux.intel.com Signed-off-by: Rafael J. Wysocki rafael.j.wysocki@intel.com --- drivers/acpi/Makefile | 1 + drivers/acpi/internal.h | 6 + drivers/acpi/property.c | 364 ++++++++++++++++++++++++++++++++++++++++++++++++ drivers/acpi/scan.c | 2 + include/acpi/acpi_bus.h | 7 + include/linux/acpi.h | 40 ++++++ 6 files changed, 420 insertions(+) create mode 100644 drivers/acpi/property.c
diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile index f96a2f1..5a21476 100644 --- a/drivers/acpi/Makefile +++ b/drivers/acpi/Makefile @@ -51,6 +51,7 @@ acpi-y += int340x_thermal.o acpi-y += power.o acpi-y += event.o acpi-y += sysfs.o +acpi-y += property.o acpi-$(CONFIG_X86) += acpi_cmos_rtc.o acpi-$(CONFIG_DEBUG_FS) += debugfs.o acpi-$(CONFIG_ACPI_NUMA) += numa.o diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h index ca0ccb4..c5ff8ba 100644 --- a/drivers/acpi/internal.h +++ b/drivers/acpi/internal.h @@ -178,4 +178,10 @@ static inline void suspend_nvs_restore(void) {} bool acpi_osi_is_win8(void); #endif
+/*-------------------------------------------------------------------------- + Device properties + -------------------------------------------------------------------------- */ +void acpi_init_properties(struct acpi_device *adev); +void acpi_free_properties(struct acpi_device *adev); + #endif /* _ACPI_INTERNAL_H_ */ diff --git a/drivers/acpi/property.c b/drivers/acpi/property.c new file mode 100644 index 0000000..c4a3e80 --- /dev/null +++ b/drivers/acpi/property.c @@ -0,0 +1,364 @@ +/* + * ACPI device specific properties support. + * + * Copyright (C) 2014, Intel Corporation + * All rights reserved. + * + * Authors: Mika Westerberg mika.westerberg@linux.intel.com + * Darren Hart dvhart@linux.intel.com + * Rafael J. Wysocki rafael.j.wysocki@intel.com + * + * 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. + */ + +#include <linux/acpi.h> +#include <linux/device.h> +#include <linux/export.h> + +#include "internal.h" + +/* ACPI _DSD device properties UUID: daffd814-6eba-4d8c-8a91-bc9bbf4aa301 */ +static const u8 prp_uuid[16] = { + 0x14, 0xd8, 0xff, 0xda, 0xba, 0x6e, 0x8c, 0x4d, + 0x8a, 0x91, 0xbc, 0x9b, 0xbf, 0x4a, 0xa3, 0x01 +}; + +static bool acpi_property_value_ok(const union acpi_object *value) +{ + int j; + + /* + * The value must be an integer, a string, a reference, or a package + * whose every element must be an integer, a string, or a reference. + */ + switch (value->type) { + case ACPI_TYPE_INTEGER: + case ACPI_TYPE_STRING: + case ACPI_TYPE_LOCAL_REFERENCE: + return true; + + case ACPI_TYPE_PACKAGE: + for (j = 0; j < value->package.count; j++) + switch (value->package.elements[j].type) { + case ACPI_TYPE_INTEGER: + case ACPI_TYPE_STRING: + case ACPI_TYPE_LOCAL_REFERENCE: + continue; + + default: + return false; + } + + return true; + } + return false; +} + +static bool acpi_properties_format_valid(const union acpi_object *properties) +{ + int i; + + for (i = 0; i < properties->package.count; i++) { + const union acpi_object *property; + + property = &properties->package.elements[i]; + /* + * Only two elements allowed, the first one must be a string and + * the second one has to satisfy certain conditions. + */ + if (property->package.count != 2 + || property->package.elements[0].type != ACPI_TYPE_STRING + || !acpi_property_value_ok(&property->package.elements[1])) + return false; + } + return true; +} + +void acpi_init_properties(struct acpi_device *adev) +{ + struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER }; + const union acpi_object *desc; + acpi_status status; + int i; + + status = acpi_evaluate_object_typed(adev->handle, "_DSD", NULL, &buf, + ACPI_TYPE_PACKAGE); + if (ACPI_FAILURE(status)) + return; + + desc = buf.pointer; + if (desc->package.count % 2) + goto fail; + + /* Look for the device properties UUID. */ + for (i = 0; i < desc->package.count; i += 2) { + const union acpi_object *uuid, *properties; + + uuid = &desc->package.elements[i]; + properties = &desc->package.elements[i + 1]; + + /* + * The first element must be a UUID and the second one must be + * a package. + */ + if (uuid->type != ACPI_TYPE_BUFFER || uuid->buffer.length != 16 + || properties->type != ACPI_TYPE_PACKAGE) + break; + + if (memcmp(uuid->buffer.pointer, prp_uuid, sizeof(prp_uuid))) + continue; + + /* + * We found the matching UUID. Now validate the format of the + * package immediately following it. + */ + if (!acpi_properties_format_valid(properties)) + break; + + adev->data.pointer = buf.pointer; + adev->data.properties = properties; + return; + } + + fail: + dev_warn(&adev->dev, "Returned _DSD data is not valid, skipping\n"); + ACPI_FREE(buf.pointer); +} + +void acpi_free_properties(struct acpi_device *adev) +{ + ACPI_FREE((void *)adev->data.pointer); + adev->data.pointer = NULL; + adev->data.properties = NULL; +} + +/** + * acpi_dev_get_property - return an ACPI property with given name + * @adev: ACPI device to get property + * @name: Name of the property + * @type: Expected property type + * @obj: Location to store the property value (if not %NULL) + * + * Look up a property with @name and store a pointer to the resulting ACPI + * object at the location pointed to by @obj if found. + * + * Callers must not attempt to free the returned objects. These objects will be + * freed by the ACPI core automatically during the removal of @adev. + * + * Return: %0 if property with @name has been found (success), + * %-EINVAL if the arguments are invalid, + * %-ENODATA if the property doesn't exist, + * %-EPROTO if the property value type doesn't match @type. + */ +int acpi_dev_get_property(struct acpi_device *adev, const char *name, + acpi_object_type type, const union acpi_object **obj) +{ + const union acpi_object *properties; + int i; + + if (!adev || !name) + return -EINVAL; + + if (!adev->data.pointer || !adev->data.properties) + return -ENODATA; + + properties = adev->data.properties; + for (i = 0; i < properties->package.count; i++) { + const union acpi_object *propname, *propvalue; + const union acpi_object *property; + + property = &properties->package.elements[i]; + + propname = &property->package.elements[0]; + propvalue = &property->package.elements[1]; + + if (!strcmp(name, propname->string.pointer)) { + if (type != ACPI_TYPE_ANY && propvalue->type != type) + return -EPROTO; + else if (obj) + *obj = propvalue; + + return 0; + } + } + return -ENODATA; +} +EXPORT_SYMBOL_GPL(acpi_dev_get_property); + +/** + * acpi_dev_get_property_array - return an ACPI array property with given name + * @adev: ACPI device to get property + * @name: Name of the property + * @type: Expected type of array elements + * @obj: Location to store a pointer to the property value (if not NULL) + * + * Look up an array property with @name and store a pointer to the resulting + * ACPI object at the location pointed to by @obj if found. + * + * Callers must not attempt to free the returned objects. Those objects will be + * freed by the ACPI core automatically during the removal of @adev. + * + * Return: %0 if array property (package) with @name has been found (success), + * %-EINVAL if the arguments are invalid, + * %-ENODATA if the property doesn't exist, + * %-EPROTO if the property is not a package or the type of its elements + * doesn't match @type. + */ +int acpi_dev_get_property_array(struct acpi_device *adev, const char *name, + acpi_object_type type, + const union acpi_object **obj) +{ + const union acpi_object *prop; + int ret, i; + + ret = acpi_dev_get_property(adev, name, ACPI_TYPE_PACKAGE, &prop); + if (ret) + return ret; + + if (type != ACPI_TYPE_ANY) { + /* Check that all elements are of correct type. */ + for (i = 0; i < prop->package.count; i++) + if (prop->package.elements[i].type != type) + return -EPROTO; + } + if (obj) + *obj = prop; + + return 0; +} +EXPORT_SYMBOL_GPL(acpi_dev_get_property_array); + +/** + * acpi_dev_get_property_reference - returns handle to the referenced object + * @adev: ACPI device to get property + * @name: Name of the property + * @size_prop: Name of the "size" property in referenced object + * @index: Index of the reference to return + * @args: Location to store the returned reference with optional arguments + * + * Find property with @name, verifify that it is a package containing at least + * one object reference and if so, store the ACPI device object pointer to the + * target object in @args->adev. + * + * If the reference includes arguments (@size_prop is not %NULL) follow the + * reference and check whether or not there is an integer property @size_prop + * under the target object and if so, whether or not its value matches the + * number of arguments that follow the reference. If there's more than one + * reference in the property value package, @index is used to select the one to + * return. + * + * Return: %0 on success, negative error code on failure. + */ +int acpi_dev_get_property_reference(struct acpi_device *adev, const char *name, + const char *size_prop, size_t index, + struct acpi_reference_args *args) +{ + const union acpi_object *element, *end; + const union acpi_object *obj; + struct acpi_device *device; + int ret, idx = 0; + + ret = acpi_dev_get_property(adev, name, ACPI_TYPE_ANY, &obj); + if (ret) + return ret; + + /* + * The simplest case is when the value is a single reference. Just + * return that reference then. + */ + if (obj->type == ACPI_TYPE_LOCAL_REFERENCE) { + if (size_prop || index) + return -EINVAL; + + ret = acpi_bus_get_device(obj->reference.handle, &device); + if (ret) + return ret; + + args->adev = device; + args->nargs = 0; + return 0; + } + + /* + * If it is not a single reference, then it is a package of + * references followed by number of ints as follows: + * + * Package () { REF, INT, REF, INT, INT } + * + * The index argument is then used to determine which reference + * the caller wants (along with the arguments). + */ + if (obj->type != ACPI_TYPE_PACKAGE || index >= obj->package.count) + return -EPROTO; + + element = obj->package.elements; + end = element + obj->package.count; + + while (element < end) { + u32 nargs, i; + + if (element->type != ACPI_TYPE_LOCAL_REFERENCE) + return -EPROTO; + + ret = acpi_bus_get_device(element->reference.handle, &device); + if (ret) + return -ENODEV; + + element++; + nargs = 0; + + if (size_prop) { + const union acpi_object *prop; + + /* + * Find out how many arguments the refenced object + * expects by reading its size_prop property. + */ + ret = acpi_dev_get_property(device, size_prop, + ACPI_TYPE_INTEGER, &prop); + if (ret) + return ret; + + nargs = prop->integer.value; + if (nargs > MAX_ACPI_REFERENCE_ARGS + || element + nargs > end) + return -EPROTO; + + /* + * Skip to the start of the arguments and verify + * that they all are in fact integers. + */ + for (i = 0; i < nargs; i++) + if (element[i].type != ACPI_TYPE_INTEGER) + return -EPROTO; + } else { + /* assume following integer elements are all args */ + for (i = 0; element + i < end; i++) { + int type = element[i].type; + + if (type == ACPI_TYPE_INTEGER) + nargs++; + else if (type == ACPI_TYPE_LOCAL_REFERENCE) + break; + else + return -EPROTO; + } + } + + if (idx++ == index) { + args->adev = device; + args->nargs = nargs; + for (i = 0; i < nargs; i++) + args->args[i] = element[i].integer.value; + + return 0; + } + + element += nargs; + } + + return -EPROTO; +} +EXPORT_SYMBOL_GPL(acpi_dev_get_property_reference); diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 0476e90..40d80ac 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -922,6 +922,7 @@ static void acpi_device_release(struct device *dev) { struct acpi_device *acpi_dev = to_acpi_device(dev);
+ acpi_free_properties(acpi_dev); acpi_free_pnp_ids(&acpi_dev->pnp); acpi_free_power_resources_lists(acpi_dev); kfree(acpi_dev); @@ -1926,6 +1927,7 @@ void acpi_init_device_object(struct acpi_device *device, acpi_handle handle, acpi_set_device_status(device, sta); acpi_device_get_busid(device); acpi_set_pnp_ids(handle, &device->pnp, type); + acpi_init_properties(device); acpi_bus_get_flags(device); device->flags.match_driver = false; device->flags.initialized = true; diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index f34a083..4757811 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -337,6 +337,12 @@ struct acpi_device_physical_node { bool put_online:1; };
+/* ACPI Device Specific Data (_DSD) */ +struct acpi_device_data { + const union acpi_object *pointer; + const union acpi_object *properties; +}; + /* Device */ struct acpi_device { int device_type; @@ -353,6 +359,7 @@ struct acpi_device { struct acpi_device_wakeup wakeup; struct acpi_device_perf performance; struct acpi_device_dir dir; + struct acpi_device_data data; struct acpi_scan_handler *handler; struct acpi_hotplug_context *hp; struct acpi_driver *driver; diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 5a92d49..f3c1b07 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -664,4 +664,44 @@ do { \ #endif #endif
+/* Device properties */ + +#define MAX_ACPI_REFERENCE_ARGS 8 +struct acpi_reference_args { + struct acpi_device *adev; + size_t nargs; + u64 args[MAX_ACPI_REFERENCE_ARGS]; +}; + +#ifdef CONFIG_ACPI +int acpi_dev_get_property(struct acpi_device *adev, const char *name, + acpi_object_type type, const union acpi_object **obj); +int acpi_dev_get_property_array(struct acpi_device *adev, const char *name, + acpi_object_type type, + const union acpi_object **obj); +int acpi_dev_get_property_reference(struct acpi_device *adev, const char *name, + const char *cells_name, size_t index, + struct acpi_reference_args *args); +#else +static inline int acpi_dev_get_property(struct acpi_device *adev, + const char *name, acpi_object_type type, + const union acpi_object **obj) +{ + return -ENXIO; +} +static inline int acpi_dev_get_property_array(struct acpi_device *adev, + const char *name, + acpi_object_type type, + const union acpi_object **obj) +{ + return -ENXIO; +} +static inline int acpi_dev_get_property_reference(struct acpi_device *adev, + const char *name, const char *cells_name, + size_t index, struct acpi_reference_args *args) +{ + return -ENXIO; +} +#endif + #endif /*_LINUX_ACPI_H*/
From: "Rafael J. Wysocki" rafael.j.wysocki@intel.com
Add a uniform interface by which device drivers can request device properties from the platform firmware by providing a property name and the corresponding data type. The purpose of it is to help to write portable code that won't depend on any particular platform firmware interface.
The following general helper functions are added:
device_property_present() device_property_read_u8() device_property_read_u16() device_property_read_u32() device_property_read_u64() device_property_read_string() device_property_read_u8_array() device_property_read_u16_array() device_property_read_u32_array() device_property_read_u64_array() device_property_read_string_array()
The first one allows the caller to check if the given property is present. The next 5 of them allow single-valued properties of various types to be retrieved in a uniform way. The remaining 5 are for reading properties with multiple values (arrays of either numbers or strings).
The interface covers both ACPI and Device Trees.
This change set includes material from Mika Westerberg and Aaron Lu.
Signed-off-by: Aaron Lu aaron.lu@intel.com Signed-off-by: Mika Westerberg mika.westerberg@linux.intel.com Acked-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Signed-off-by: Rafael J. Wysocki rafael.j.wysocki@intel.com --- drivers/acpi/property.c | 169 +++++++++++++++++++++++++++++ drivers/base/Makefile | 2 +- drivers/base/property.c | 276 +++++++++++++++++++++++++++++++++++++++++++++++ drivers/of/base.c | 102 +++++++++++++++--- include/linux/acpi.h | 32 ++++++ include/linux/of.h | 22 ++++ include/linux/property.h | 53 +++++++++ 7 files changed, 641 insertions(+), 15 deletions(-) create mode 100644 drivers/base/property.c create mode 100644 include/linux/property.h
diff --git a/drivers/acpi/property.c b/drivers/acpi/property.c index c4a3e80..b586b58 100644 --- a/drivers/acpi/property.c +++ b/drivers/acpi/property.c @@ -362,3 +362,172 @@ int acpi_dev_get_property_reference(struct acpi_device *adev, const char *name, return -EPROTO; } EXPORT_SYMBOL_GPL(acpi_dev_get_property_reference); + +int acpi_dev_prop_get(struct acpi_device *adev, const char *propname, + void **valptr) +{ + return acpi_dev_get_property(adev, propname, ACPI_TYPE_ANY, + (const union acpi_object **)valptr); +} + +int acpi_dev_prop_read(struct acpi_device *adev, const char *propname, + enum dev_prop_type proptype, void *val) +{ + const union acpi_object *obj; + int ret = -EINVAL; + + if (!val) + return -EINVAL; + + if (proptype >= DEV_PROP_U8 && proptype <= DEV_PROP_U64) { + ret = acpi_dev_get_property(adev, propname, ACPI_TYPE_INTEGER, &obj); + if (ret) + return ret; + + switch (proptype) { + case DEV_PROP_U8: + if (obj->integer.value > U8_MAX) + return -EOVERFLOW; + *(u8 *)val = obj->integer.value; + break; + case DEV_PROP_U16: + if (obj->integer.value > U16_MAX) + return -EOVERFLOW; + *(u16 *)val = obj->integer.value; + break; + case DEV_PROP_U32: + if (obj->integer.value > U32_MAX) + return -EOVERFLOW; + *(u32 *)val = obj->integer.value; + break; + default: + *(u64 *)val = obj->integer.value; + break; + } + } else if (proptype == DEV_PROP_STRING) { + ret = acpi_dev_get_property(adev, propname, ACPI_TYPE_STRING, &obj); + if (ret) + return ret; + + *(char **)val = obj->string.pointer; + } + return ret; +} + +static int acpi_copy_property_array_u8(const union acpi_object *items, u8 *val, + size_t nval) +{ + int i; + + for (i = 0; i < nval; i++) { + if (items[i].type != ACPI_TYPE_INTEGER) + return -EPROTO; + if (items[i].integer.value > U8_MAX) + return -EOVERFLOW; + + val[i] = items[i].integer.value; + } + return 0; +} + +static int acpi_copy_property_array_u16(const union acpi_object *items, + u16 *val, size_t nval) +{ + int i; + + for (i = 0; i < nval; i++) { + if (items[i].type != ACPI_TYPE_INTEGER) + return -EPROTO; + if (items[i].integer.value > U16_MAX) + return -EOVERFLOW; + + val[i] = items[i].integer.value; + } + return 0; +} + +static int acpi_copy_property_array_u32(const union acpi_object *items, + u32 *val, size_t nval) +{ + int i; + + for (i = 0; i < nval; i++) { + if (items[i].type != ACPI_TYPE_INTEGER) + return -EPROTO; + if (items[i].integer.value > U32_MAX) + return -EOVERFLOW; + + val[i] = items[i].integer.value; + } + return 0; +} + +static int acpi_copy_property_array_u64(const union acpi_object *items, + u64 *val, size_t nval) +{ + int i; + + for (i = 0; i < nval; i++) { + if (items[i].type != ACPI_TYPE_INTEGER) + return -EPROTO; + + val[i] = items[i].integer.value; + } + return 0; +} + +static int acpi_copy_property_array_string(const union acpi_object *items, + char **val, size_t nval) +{ + int i; + + for (i = 0; i < nval; i++) { + if (items[i].type != ACPI_TYPE_STRING) + return -EPROTO; + + val[i] = items[i].string.pointer; + } + return 0; +} + +int acpi_dev_prop_read_array(struct acpi_device *adev, const char *propname, + enum dev_prop_type proptype, void *val, + size_t nval) +{ + const union acpi_object *obj; + const union acpi_object *items; + int ret; + + ret = acpi_dev_get_property_array(adev, propname, ACPI_TYPE_ANY, &obj); + if (ret) + return ret; + + if (!val) + return obj->package.count; + + if (nval > obj->package.count) + return -EOVERFLOW; + + items = obj->package.elements; + switch (proptype) { + case DEV_PROP_U8: + ret = acpi_copy_property_array_u8(items, (u8 *)val, nval); + break; + case DEV_PROP_U16: + ret = acpi_copy_property_array_u16(items, (u16 *)val, nval); + break; + case DEV_PROP_U32: + ret = acpi_copy_property_array_u32(items, (u32 *)val, nval); + break; + case DEV_PROP_U64: + ret = acpi_copy_property_array_u64(items, (u64 *)val, nval); + break; + case DEV_PROP_STRING: + ret = acpi_copy_property_array_string(items, (char **)val, nval); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} diff --git a/drivers/base/Makefile b/drivers/base/Makefile index 6922cd6..53c3fe1 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -4,7 +4,7 @@ obj-y := component.o core.o bus.o dd.o syscore.o \ driver.o class.o platform.o \ cpu.o firmware.o init.o map.o devres.o \ attribute_container.o transport_class.o \ - topology.o container.o + topology.o container.o property.o obj-$(CONFIG_DEVTMPFS) += devtmpfs.o obj-$(CONFIG_DMA_CMA) += dma-contiguous.o obj-y += power/ diff --git a/drivers/base/property.c b/drivers/base/property.c new file mode 100644 index 0000000..514ca66 --- /dev/null +++ b/drivers/base/property.c @@ -0,0 +1,276 @@ +/* + * property.c - Unified device property interface. + * + * Copyright (C) 2014, Intel Corporation + * Authors: Rafael J. Wysocki rafael.j.wysocki@intel.com + * Mika Westerberg mika.westerberg@linux.intel.com + * + * 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. + */ + +#include <linux/property.h> +#include <linux/export.h> +#include <linux/acpi.h> +#include <linux/of.h> + +/** + * device_property_present - check if a property of a device is present + * @dev: Device whose property is being checked + * @propname: Name of the property + * + * Check if property @propname is present in the device firmware description. + */ +bool device_property_present(struct device *dev, const char *propname) +{ + if (IS_ENABLED(CONFIG_OF) && dev->of_node) + return of_property_read_bool(dev->of_node, propname); + + return !acpi_dev_prop_get(ACPI_COMPANION(dev), propname, NULL); +} +EXPORT_SYMBOL_GPL(device_property_present); + +#define DEVICE_PROPERTY_READ(_dev_, _propname_, _type_, _proptype_, _val_) \ + IS_ENABLED(CONFIG_OF) && _dev_->of_node ? \ + of_property_read_##_type_(_dev_->of_node, _propname_, _val_) : \ + acpi_dev_prop_read(ACPI_COMPANION(_dev_), _propname_, \ + _proptype_, _val_) + +/** + * device_property_read_u8 - return a u8 property of a device + * @dev: Device to get the property of + * @propname: Name of the property + * @val: The value is stored here + * + * Function reads property @propname from the device firmware description and + * stores the value into @val if found. The value is checked to be of type u8. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO or %-EILSEQ if the property type is not u8, + * %-EOVERFLOW if the property value is out of bounds of u8. + */ +int device_property_read_u8(struct device *dev, const char *propname, u8 *val) +{ + return DEVICE_PROPERTY_READ(dev, propname, u8, DEV_PROP_U8, val); +} +EXPORT_SYMBOL_GPL(device_property_read_u8); + +/** + * device_property_read_u16 - return a u16 property of a device + * @dev: Device to get the property of + * @propname: Name of the property + * @val: The value is stored here + * + * Function reads property @propname from the device firmware description and + * stores the value into @val if found. The value is checked to be of type u16. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO or %-EILSEQ if the property type is not u16, + * %-EOVERFLOW if the property value is out of bounds of u16. + */ +int device_property_read_u16(struct device *dev, const char *propname, u16 *val) +{ + return DEVICE_PROPERTY_READ(dev, propname, u16, DEV_PROP_U16, val); +} +EXPORT_SYMBOL_GPL(device_property_read_u16); + +/** + * device_property_read_u32 - return a u32 property of a device + * @dev: Device to get the property of + * @propname: Name of the property + * @val: The value is stored here + * + * Function reads property @propname from the device firmware description and + * stores the value into @val if found. The value is checked to be of type u32. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO or %-EILSEQ if the property type is not u32, + * %-EOVERFLOW if the property value is out of bounds of u32. + */ +int device_property_read_u32(struct device *dev, const char *propname, u32 *val) +{ + return DEVICE_PROPERTY_READ(dev, propname, u32, DEV_PROP_U32, val); +} +EXPORT_SYMBOL_GPL(device_property_read_u32); + +/** + * device_property_read_u64 - return a u64 property of a device + * @dev: Device to get the property of + * @propname: Name of the property + * @val: The value is stored here + * + * Function reads property @propname from the device firmware description and + * stores the value into @val if found. The value is checked to be of type u64. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO or %-EILSEQ if the property type is not u64, + * %-EOVERFLOW if the property value is out of bounds of u64. + */ +int device_property_read_u64(struct device *dev, const char *propname, u64 *val) +{ + return DEVICE_PROPERTY_READ(dev, propname, u64, DEV_PROP_U64, val); +} +EXPORT_SYMBOL_GPL(device_property_read_u64); + +/** + * device_property_read_string - return a string property of a device + * @dev: Device to get the property of + * @propname: Name of the property + * @val: The value is stored here + * + * Function reads property @propname from the device firmware description and + * stores the value into @val if found. The value is checked to be a string. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO or %-EILSEQ if the property type is not a string. + */ +int device_property_read_string(struct device *dev, const char *propname, + const char **val) +{ + return DEVICE_PROPERTY_READ(dev, propname, string, DEV_PROP_STRING, val); +} +EXPORT_SYMBOL_GPL(device_property_read_string); + +#define OF_DEV_PROP_READ_ARRAY(node, propname, type, val, nval) \ + (val) ? of_property_read_##type##_array((node), (propname), (val), (nval)) \ + : of_property_count_elems_of_size((node), (propname), sizeof(type)) + +#define DEVICE_PROPERTY_READ_ARRAY(_dev_, _propname_, _type_, _proptype_, _val_, _nval_) \ + IS_ENABLED(CONFIG_OF) && _dev_->of_node ? \ + (OF_DEV_PROP_READ_ARRAY(_dev_->of_node, _propname_, _type_, \ + _val_, _nval_)) : \ + acpi_dev_prop_read_array(ACPI_COMPANION(_dev_), _propname_, \ + _proptype_, _val_, _nval_) + +/** + * device_property_read_u8_array - return a u8 array property of a device + * @dev: Device to get the property of + * @propname: Name of the property + * @val: The values are stored here + * @nval: Size of the @val array + * + * Function reads an array of u8 properties with @propname from the device + * firmware description and stores them to @val if found. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO if the property is not an array of numbers, + * %-EOVERFLOW if the size of the property is not as expected. + */ +int device_property_read_u8_array(struct device *dev, const char *propname, + u8 *val, size_t nval) +{ + return DEVICE_PROPERTY_READ_ARRAY(dev, propname, u8, DEV_PROP_U8, val, + nval); +} +EXPORT_SYMBOL_GPL(device_property_read_u8_array); + +/** + * device_property_read_u16_array - return a u16 array property of a device + * @dev: Device to get the property of + * @propname: Name of the property + * @val: The values are stored here + * @nval: Size of the @val array + * + * Function reads an array of u16 properties with @propname from the device + * firmware description and stores them to @val if found. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO if the property is not an array of numbers, + * %-EOVERFLOW if the size of the property is not as expected. + */ +int device_property_read_u16_array(struct device *dev, const char *propname, + u16 *val, size_t nval) +{ + return DEVICE_PROPERTY_READ_ARRAY(dev, propname, u16, DEV_PROP_U16, val, + nval); +} +EXPORT_SYMBOL_GPL(device_property_read_u16_array); + +/** + * device_property_read_u32_array - return a u32 array property of a device + * @dev: Device to get the property of + * @propname: Name of the property + * @val: The values are stored here + * @nval: Size of the @val array + * + * Function reads an array of u32 properties with @propname from the device + * firmware description and stores them to @val if found. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO if the property is not an array of numbers, + * %-EOVERFLOW if the size of the property is not as expected. + */ +int device_property_read_u32_array(struct device *dev, const char *propname, + u32 *val, size_t nval) +{ + return DEVICE_PROPERTY_READ_ARRAY(dev, propname, u32, DEV_PROP_U32, val, + nval); +} +EXPORT_SYMBOL_GPL(device_property_read_u32_array); + +/** + * device_property_read_u64_array - return a u64 array property of a device + * @dev: Device to get the property of + * @propname: Name of the property + * @val: The values are stored here + * @nval: Size of the @val array + * + * Function reads an array of u64 properties with @propname from the device + * firmware description and stores them to @val if found. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO if the property is not an array of numbers, + * %-EOVERFLOW if the size of the property is not as expected. + */ +int device_property_read_u64_array(struct device *dev, const char *propname, + u64 *val, size_t nval) +{ + return DEVICE_PROPERTY_READ_ARRAY(dev, propname, u64, DEV_PROP_U64, val, + nval); +} +EXPORT_SYMBOL_GPL(device_property_read_u64_array); + +/** + * device_property_read_string_array - return a string array property of device + * @dev: Device to get the property of + * @propname: Name of the property + * @val: The values are stored here + * @nval: Size of the @val array + * + * Function reads an array of string properties with @propname from the device + * firmware description and stores them to @val if found. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO or %-EILSEQ if the property is not an array of strings, + * %-EOVERFLOW if the size of the property is not as expected. + */ +int device_property_read_string_array(struct device *dev, const char *propname, + char **val, size_t nval) +{ + return IS_ENABLED(CONFIG_OF) && dev->of_node ? + of_property_read_string_array(dev->of_node, propname, val, nval) : + acpi_dev_prop_read_array(ACPI_COMPANION(dev), propname, + DEV_PROP_STRING, val, nval); +} +EXPORT_SYMBOL_GPL(device_property_read_string_array); diff --git a/drivers/of/base.c b/drivers/of/base.c index 2305dc0..74ab1b8 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -1250,6 +1250,39 @@ int of_property_read_u64(const struct device_node *np, const char *propname, EXPORT_SYMBOL_GPL(of_property_read_u64);
/** + * of_property_read_u64_array - Find and read an array of 64 bit integers + * from a property. + * + * @np: device node from which the property value is to be read. + * @propname: name of the property to be searched. + * @out_values: pointer to return value, modified only if return value is 0. + * @sz: number of array elements to read + * + * Search for a property in a device node and read 64-bit value(s) from + * it. Returns 0 on success, -EINVAL if the property does not exist, + * -ENODATA if property does not have a value, and -EOVERFLOW if the + * property data isn't large enough. + * + * The out_values is modified only if a valid u64 value can be decoded. + */ +int of_property_read_u64_array(const struct device_node *np, + const char *propname, u64 *out_values, + size_t sz) +{ + const __be32 *val = of_find_property_value_of_size(np, propname, + (sz * sizeof(*out_values))); + + if (IS_ERR(val)) + return PTR_ERR(val); + + while (sz--) { + *out_values++ = of_read_number(val, 2); + val += 2; + } + return 0; +} + +/** * of_property_read_string - Find and read a string from a property * @np: device node from which the property value is to be read. * @propname: name of the property to be searched. @@ -1362,6 +1395,27 @@ int of_property_match_string(struct device_node *np, const char *propname, } EXPORT_SYMBOL_GPL(of_property_match_string);
+static int property_count_strings(struct property *prop) +{ + const char *p; + size_t l = 0, total = 0; + int i = 0; + + if (!prop) + return -EINVAL; + if (!prop->value) + return -ENODATA; + if (strnlen(prop->value, prop->length) >= prop->length) + return -EILSEQ; + + p = prop->value; + + for (i = 0; total < prop->length; total += l, p += l, i++) + l = strlen(p) + 1; + + return i; +} + /** * of_property_count_strings - Find and return the number of strings from a * multiple strings property. @@ -1376,26 +1430,46 @@ EXPORT_SYMBOL_GPL(of_property_match_string); */ int of_property_count_strings(struct device_node *np, const char *propname) { + return property_count_strings(of_find_property(np, propname, NULL)); +} +EXPORT_SYMBOL_GPL(of_property_count_strings); + +/** + * of_property_read_string_array - Read an array of strings from a multiple + * strings property. + * @np: device node from which the property value is to be read. + * @propname: name of the property to be searched. + * @out_strs: output array of string pointers. + * @sz: number of array elements to read. + * + * Search for a property in a device tree node and retrieve a list of + * terminated string values (pointer to data, not a copy) in that property. + * + * If @out_strs is NULL, the number of strings in the property is returned. + */ +int of_property_read_string_array(struct device_node *np, const char *propname, + char **out_strs, size_t sz) +{ struct property *prop = of_find_property(np, propname, NULL); - int i = 0; - size_t l = 0, total = 0; - const char *p; + char *p = prop->value; + size_t total = 0; + int i;
- if (!prop) - return -EINVAL; - if (!prop->value) - return -ENODATA; - if (strnlen(prop->value, prop->length) >= prop->length) - return -EILSEQ; + i = property_count_strings(prop); + if (!out_strs || i < 0) + return i;
- p = prop->value; + if (i < sz) + return -EOVERFLOW;
- for (i = 0; total < prop->length; total += l, p += l, i++) - l = strlen(p) + 1; + while (total < prop->length && i < sz) { + size_t l = strlen(p) + 1;
- return i; + out_strs[i++] = p; + total += l; + p += l; + } } -EXPORT_SYMBOL_GPL(of_property_count_strings);
void of_print_phandle_args(const char *msg, const struct of_phandle_args *args) { diff --git a/include/linux/acpi.h b/include/linux/acpi.h index f3c1b07..6eb7c40 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -28,6 +28,7 @@ #include <linux/errno.h> #include <linux/ioport.h> /* for struct resource */ #include <linux/device.h> +#include <linux/property.h>
#ifndef _LINUX #define _LINUX @@ -682,6 +683,14 @@ int acpi_dev_get_property_array(struct acpi_device *adev, const char *name, int acpi_dev_get_property_reference(struct acpi_device *adev, const char *name, const char *cells_name, size_t index, struct acpi_reference_args *args); + +int acpi_dev_prop_get(struct acpi_device *adev, const char *propname, + void **valptr); +int acpi_dev_prop_read(struct acpi_device *adev, const char *propname, + enum dev_prop_type proptype, void *val); +int acpi_dev_prop_read_array(struct acpi_device *adev, const char *propname, + enum dev_prop_type proptype, void *val, + size_t nval); #else static inline int acpi_dev_get_property(struct acpi_device *adev, const char *name, acpi_object_type type, @@ -702,6 +711,29 @@ static inline int acpi_dev_get_property_reference(struct acpi_device *adev, { return -ENXIO; } + +static inline int acpi_dev_prop_get(struct acpi_device *adev, + const char *propname, + void **valptr) +{ + return -ENXIO; +} + +static inline int acpi_dev_prop_read(struct acpi_device *adev, + const char *propname, + enum dev_prop_type proptype, void *val) +{ + return -ENXIO; +} + +static inline int acpi_dev_prop_read_array(struct acpi_device *adev, + const char *propname, + enum dev_prop_type proptype, + void *val, size_t nval) +{ + return -ENXIO; +} + #endif
#endif /*_LINUX_ACPI_H*/ diff --git a/include/linux/of.h b/include/linux/of.h index 6545e7a..25efe42 100644 --- a/include/linux/of.h +++ b/include/linux/of.h @@ -23,6 +23,7 @@ #include <linux/spinlock.h> #include <linux/topology.h> #include <linux/notifier.h> +#include <linux/property.h>
#include <asm/byteorder.h> #include <asm/errno.h> @@ -263,6 +264,10 @@ extern int of_property_read_u32_array(const struct device_node *np, size_t sz); extern int of_property_read_u64(const struct device_node *np, const char *propname, u64 *out_value); +extern int of_property_read_u64_array(const struct device_node *np, + const char *propname, + u64 *out_values, + size_t sz);
extern int of_property_read_string(struct device_node *np, const char *propname, @@ -275,6 +280,9 @@ extern int of_property_match_string(struct device_node *np, const char *string); extern int of_property_count_strings(struct device_node *np, const char *propname); +extern int of_property_read_string_array(struct device_node *np, + const char *propname, + char **out_strs, size_t sz); extern int of_device_is_compatible(const struct device_node *device, const char *); extern int of_device_is_available(const struct device_node *device); @@ -479,6 +487,13 @@ static inline int of_property_read_u32_array(const struct device_node *np, return -ENOSYS; }
+static inline int of_property_read_u64_array(const struct device_node *np, + const char *propname, + u64 *out_values, size_t sz) +{ + return -ENOSYS; +} + static inline int of_property_read_string(struct device_node *np, const char *propname, const char **out_string) @@ -499,6 +514,13 @@ static inline int of_property_count_strings(struct device_node *np, return -ENOSYS; }
+static inline int of_property_read_string_array(struct device_node *np, + const char *propname, + char **out_strs, size_t sz) +{ + return -ENOSYS; +} + static inline const void *of_get_property(const struct device_node *node, const char *name, int *lenp) diff --git a/include/linux/property.h b/include/linux/property.h new file mode 100644 index 0000000..7a3555f --- /dev/null +++ b/include/linux/property.h @@ -0,0 +1,53 @@ +/* + * property.h - Unified device property interface. + * + * Copyright (C) 2014, Intel Corporation + * Authors: Rafael J. Wysocki rafael.j.wysocki@intel.com + * Mika Westerberg mika.westerberg@linux.intel.com + * + * 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. + */ + +#ifndef _LINUX_PROPERTY_H_ +#define _LINUX_PROPERTY_H_ + +#include <linux/types.h> + +struct device; + +enum dev_prop_type { + DEV_PROP_U8, + DEV_PROP_U16, + DEV_PROP_U32, + DEV_PROP_U64, + DEV_PROP_STRING, + DEV_PROP_MAX, +}; + +bool device_property_present(struct device *dev, const char *propname); +int device_property_read_u8(struct device *dev, const char *propname, u8 *val); +int device_property_read_u16(struct device *dev, const char *propname, u16 *val); +int device_property_read_u32(struct device *dev, const char *propname, u32 *val); +int device_property_read_u64(struct device *dev, const char *propname, u64 *val); +int device_property_read_string(struct device *dev, const char *propname, + const char **val); +int device_property_read_u8_array(struct device *dev, const char *propname, + u8 *val, size_t nval); +int device_property_read_u16_array(struct device *dev, const char *propname, + u16 *val, size_t nval); +int device_property_read_u32_array(struct device *dev, const char *propname, + u32 *val, size_t nval); +int device_property_read_u64_array(struct device *dev, const char *propname, + u64 *val, size_t nval); +int device_property_read_string_array(struct device *dev, const char *propname, + char **val, size_t nval); + +static inline bool device_property_read_bool(struct device *dev, + const char *propname) +{ + return device_property_present(dev, propname); +} + +#endif /* _LINUX_PROPERTY_H_ */
From: Mika Westerberg mika.westerberg@linux.intel.com
We have lots of existing Device Tree enabled drivers and allocating separate _HID for each is not feasible. Instead we allocate special _HID "PRP0001" that means that the match should be done using Device Tree compatible property using driver's .of_match_table instead if the driver is missing .acpi_match_table.
If there is a need to distinguish from where the device is enumerated (DT/ACPI) driver can check dev->of_node or ACPI_COMPATION(dev).
Signed-off-by: Mika Westerberg mika.westerberg@linux.intel.com Reviewed-by: Grant Likely grant.likely@linaro.org Signed-off-by: Rafael J. Wysocki rafael.j.wysocki@intel.com --- drivers/acpi/property.c | 34 +++++++++++++++++ drivers/acpi/scan.c | 97 +++++++++++++++++++++++++++++++++++++++++++------ include/acpi/acpi_bus.h | 1 + include/linux/acpi.h | 8 +--- 4 files changed, 123 insertions(+), 17 deletions(-)
diff --git a/drivers/acpi/property.c b/drivers/acpi/property.c index b586b58..f01fb41 100644 --- a/drivers/acpi/property.c +++ b/drivers/acpi/property.c @@ -76,6 +76,37 @@ static bool acpi_properties_format_valid(const union acpi_object *properties) return true; }
+static void acpi_init_of_compatible(struct acpi_device *adev) +{ + const union acpi_object *of_compatible; + struct acpi_hardware_id *hwid; + bool acpi_of = false; + + /* + * Check if the special PRP0001 ACPI ID is present and in that + * case we fill in Device Tree compatible properties for this + * device. + */ + list_for_each_entry(hwid, &adev->pnp.ids, list) { + if (!strcmp(hwid->id, "PRP0001")) { + acpi_of = true; + break; + } + } + + if (!acpi_of) + return; + + if (acpi_dev_get_property_array(adev, "compatible", ACPI_TYPE_STRING, + &of_compatible)) { + acpi_handle_warn(adev->handle, + "PRP0001 requires compatible property\n"); + return; + } + + adev->data.of_compatible = of_compatible; +} + void acpi_init_properties(struct acpi_device *adev) { struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER }; @@ -119,6 +150,8 @@ void acpi_init_properties(struct acpi_device *adev)
adev->data.pointer = buf.pointer; adev->data.properties = properties; + + acpi_init_of_compatible(adev); return; }
@@ -130,6 +163,7 @@ void acpi_init_properties(struct acpi_device *adev) void acpi_free_properties(struct acpi_device *adev) { ACPI_FREE((void *)adev->data.pointer); + adev->data.of_compatible = NULL; adev->data.pointer = NULL; adev->data.properties = NULL; } diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 40d80ac..78bfea2 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -124,17 +124,51 @@ static int create_modalias(struct acpi_device *acpi_dev, char *modalias, if (list_empty(&acpi_dev->pnp.ids)) return 0;
- len = snprintf(modalias, size, "acpi:"); - size -= len; - - list_for_each_entry(id, &acpi_dev->pnp.ids, list) { - count = snprintf(&modalias[len], size, "%s:", id->id); - if (count < 0) - return -EINVAL; - if (count >= size) - return -ENOMEM; - len += count; - size -= count; + /* + * If the device has PRP0001 we expose DT compatible modalias + * instead in form of of:NnameTCcompatible. + */ + if (acpi_dev->data.of_compatible) { + struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER }; + const union acpi_object *of_compatible, *obj; + char *c; + int i; + + acpi_get_name(acpi_dev->handle, ACPI_SINGLE_NAME, &buf); + /* DT strings are all in lower case */ + for (c = buf.pointer; *c != '\0'; c++) + *c = tolower(*c); + + len = snprintf(modalias, size, "of:N%sT", (char *)buf.pointer); + ACPI_FREE(buf.pointer); + + of_compatible = acpi_dev->data.of_compatible; + for (i = 0; i < of_compatible->package.count; i++) { + obj = &of_compatible->package.elements[i]; + + count = snprintf(&modalias[len], size, "C%s", + obj->string.pointer); + if (count < 0) + return -EINVAL; + if (count >= size) + return -ENOMEM; + + len += count; + size -= count; + } + } else { + len = snprintf(modalias, size, "acpi:"); + size -= len; + + list_for_each_entry(id, &acpi_dev->pnp.ids, list) { + count = snprintf(&modalias[len], size, "%s:", id->id); + if (count < 0) + return -EINVAL; + if (count >= size) + return -ENOMEM; + len += count; + size -= count; + } }
modalias[len] = '\0'; @@ -902,6 +936,47 @@ int acpi_match_device_ids(struct acpi_device *device, } EXPORT_SYMBOL(acpi_match_device_ids);
+/* Performs match against special "PRP0001" shoehorn ACPI ID */ +static bool acpi_of_driver_match_device(struct device *dev, + const struct device_driver *drv) +{ + const union acpi_object *of_compatible; + struct acpi_device *adev; + int i; + + adev = ACPI_COMPANION(dev); + if (!adev) + return false; + + of_compatible = adev->data.of_compatible; + if (!drv->of_match_table || !of_compatible) + return false; + + /* Now we can look for the driver DT compatible strings */ + for (i = 0; i < of_compatible->package.count; i++) { + const struct of_device_id *id; + const union acpi_object *obj; + + obj = &of_compatible->package.elements[i]; + + for (id = drv->of_match_table; id->compatible[0]; id++) + if (!strcasecmp(obj->string.pointer, id->compatible)) + return true; + } + + return false; +} + +bool acpi_driver_match_device(struct device *dev, + const struct device_driver *drv) +{ + if (!drv->acpi_match_table) + return acpi_of_driver_match_device(dev, drv); + + return !!acpi_match_device(drv->acpi_match_table, dev); +} +EXPORT_SYMBOL_GPL(acpi_driver_match_device); + static void acpi_free_power_resources_lists(struct acpi_device *device) { int i; diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index 4757811..f59cbf8 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -341,6 +341,7 @@ struct acpi_device_physical_node { struct acpi_device_data { const union acpi_object *pointer; const union acpi_object *properties; + const union acpi_object *of_compatible; };
/* Device */ diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 6eb7c40..dc3f75f 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -429,12 +429,8 @@ extern int acpi_nvs_for_each_region(int (*func)(__u64, __u64, void *), const struct acpi_device_id *acpi_match_device(const struct acpi_device_id *ids, const struct device *dev);
-static inline bool acpi_driver_match_device(struct device *dev, - const struct device_driver *drv) -{ - return !!acpi_match_device(drv->acpi_match_table, dev); -} - +extern bool acpi_driver_match_device(struct device *dev, + const struct device_driver *drv); int acpi_device_uevent_modalias(struct device *, struct kobj_uevent_env *); int acpi_device_modalias(struct device *, char *, int);
From: "Rafael J. Wysocki" rafael.j.wysocki@intel.com
Add new generic routines are provided for retrieving properties from device description objects in the platform firmware in case there are no struct device objects for them (either those objects have not been created yet or they do not exist at all).
The following functions are provided:
fwnode_property_present() fwnode_property_read_u8() fwnode_property_read_u16() fwnode_property_read_u32() fwnode_property_read_u64() fwnode_property_read_string() fwnode_property_read_u8_array() fwnode_property_read_u16_array() fwnode_property_read_u32_array() fwnode_property_read_u64_array() fwnode_property_read_string_array()
in analogy with the corresponding functions for struct device added previously. For all of them, the first argument is a pointer to struct fwnode_handle (new type) that allows a device description object (depending on what platform firmware interface is in use) to be obtained.
Add a new macro device_for_each_child_node() for iterating over the children of the device description object associated with a given device and a new function device_get_child_node_count() returning the number of a given device's child nodes.
The interface covers both ACPI and Device Trees.
Suggested-by: Grant Likely grant.likely@linaro.org Acked-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Signed-off-by: Rafael J. Wysocki rafael.j.wysocki@intel.com --- drivers/acpi/scan.c | 21 +++ drivers/base/property.c | 349 +++++++++++++++++++++++++++++++++++++++++++++++ include/acpi/acpi_bus.h | 17 +++ include/linux/acpi.h | 26 ++++ include/linux/of.h | 22 +++ include/linux/property.h | 54 ++++++++ 6 files changed, 489 insertions(+)
diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 78bfea2..4e585b9 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -1380,6 +1380,26 @@ int acpi_device_add(struct acpi_device *device, return result; }
+struct acpi_device *acpi_get_next_child(struct device *dev, + struct acpi_device *child) +{ + struct acpi_device *adev = ACPI_COMPANION(dev); + struct list_head *head, *next; + + if (!adev) + return NULL; + + head = &adev->children; + if (list_empty(head)) + return NULL; + + if (!child) + return list_first_entry(head, struct acpi_device, node); + + next = child->node.next; + return next == head ? NULL : list_entry(next, struct acpi_device, node); +} + /* -------------------------------------------------------------------------- Driver Management -------------------------------------------------------------------------- */ @@ -1999,6 +2019,7 @@ void acpi_init_device_object(struct acpi_device *device, acpi_handle handle, device->device_type = type; device->handle = handle; device->parent = acpi_bus_get_parent(handle); + device->fwnode.type = FWNODE_ACPI; acpi_set_device_status(device, sta); acpi_device_get_busid(device); acpi_set_pnp_ids(handle, &device->pnp, type); diff --git a/drivers/base/property.c b/drivers/base/property.c index 514ca66..04b60a7 100644 --- a/drivers/base/property.c +++ b/drivers/base/property.c @@ -142,6 +142,151 @@ int device_property_read_string(struct device *dev, const char *propname, } EXPORT_SYMBOL_GPL(device_property_read_string);
+/** + * fwnode_property_present - check if a property of a firmware node is present + * @fwnode: Firmware node whose property to check + * @propname: Name of the property + */ +bool fwnode_property_present(struct fwnode_handle *fwnode, const char *propname) +{ + if (is_of_node(fwnode)) + return of_property_read_bool(of_node(fwnode), propname); + else if (is_acpi_node(fwnode)) + return !acpi_dev_prop_get(acpi_node(fwnode), propname, NULL); + + return false; +} +EXPORT_SYMBOL_GPL(fwnode_property_present); + +#define FWNODE_PROPERTY_READ(_fwnode_, _propname_, _type_, _proptype_, _val_) \ +({ \ + int _ret_; \ + if (is_of_node(_fwnode_)) \ + _ret_ = of_property_read_##_type_(of_node(_fwnode_), \ + _propname_, _val_); \ + else if (is_acpi_node(_fwnode_)) \ + _ret_ = acpi_dev_prop_read(acpi_node(_fwnode_), _propname_, \ + _proptype_, _val_); \ + else \ + _ret_ = -ENXIO; \ + _ret_; \ +}) + +/** + * fwnode_property_read_u8 - return a u8 property of a firmware node + * @fwnode: Firmware node to get the property of + * @propname: Name of the property + * @val: The value is stored here + * + * Read property @propname from the given firmware node and store the value into + * @val if found. The value is checked to be of type u8. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO or %-EILSEQ if the property type is not u8, + * %-EOVERFLOW if the property value is out of bounds of u8, + * %-ENXIO if no suitable firmware interface is present. + */ +int fwnode_property_read_u8(struct fwnode_handle *fwnode, const char *propname, + u8 *val) +{ + return FWNODE_PROPERTY_READ(fwnode, propname, u8, DEV_PROP_U8, val); +} +EXPORT_SYMBOL_GPL(fwnode_property_read_u8); + +/** + * fwnode_property_read_u16 - return a u16 property of a firmware node + * @fwnode: Firmware node to get the property of + * @propname: Name of the property + * @val: The value is stored here + * + * Read property @propname from the given firmware node and store the value into + * @val if found. The value is checked to be of type u16. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO or %-EILSEQ if the property type is not u16, + * %-EOVERFLOW if the property value is out of bounds of u16, + * %-ENXIO if no suitable firmware interface is present. + */ +int fwnode_property_read_u16(struct fwnode_handle *fwnode, const char *propname, + u16 *val) +{ + return FWNODE_PROPERTY_READ(fwnode, propname, u16, DEV_PROP_U16, val); +} +EXPORT_SYMBOL_GPL(fwnode_property_read_u16); + +/** + * fwnode_property_read_u32 - return a u32 property of a firmware node + * @fwnode: Firmware node to get the property of + * @propname: Name of the property + * @val: The value is stored here + * + * Read property @propname from the given firmware node and store the value into + * @val if found. The value is checked to be of type u32. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO or %-EILSEQ if the property type is not u32, + * %-EOVERFLOW if the property value is out of bounds of u32, + * %-ENXIO if no suitable firmware interface is present. + */ +int fwnode_property_read_u32(struct fwnode_handle *fwnode, const char *propname, + u32 *val) +{ + return FWNODE_PROPERTY_READ(fwnode, propname, u32, DEV_PROP_U32, val); +} +EXPORT_SYMBOL_GPL(fwnode_property_read_u32); + +/** + * fwnode_property_read_u64 - return a u64 property of a firmware node + * @fwnode: Firmware node to get the property of + * @propname: Name of the property + * @val: The value is stored here + * + * Read property @propname from the given firmware node and store the value into + * @val if found. The value is checked to be of type u64. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO or %-EILSEQ if the property type is not u64, + * %-EOVERFLOW if the property value is out of bounds of u64, + * %-ENXIO if no suitable firmware interface is present. + */ +int fwnode_property_read_u64(struct fwnode_handle *fwnode, const char *propname, + u64 *val) +{ + return FWNODE_PROPERTY_READ(fwnode, propname, u64, DEV_PROP_U64, val); +} +EXPORT_SYMBOL_GPL(fwnode_property_read_u64); + +/** + * fwnode_property_read_string - return a string property of a firmware node + * @fwnode: Firmware node to get the property of + * @propname: Name of the property + * @val: The value is stored here + * + * Read property @propname from the given firmware node and store the value into + * @val if found. The value is checked to be a string. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO or %-EILSEQ if the property is not a string, + * %-ENXIO if no suitable firmware interface is present. + */ +int fwnode_property_read_string(struct fwnode_handle *fwnode, + const char *propname, const char **val) +{ + return FWNODE_PROPERTY_READ(fwnode, propname, string, DEV_PROP_STRING, + val); +} +EXPORT_SYMBOL_GPL(fwnode_property_read_string); + #define OF_DEV_PROP_READ_ARRAY(node, propname, type, val, nval) \ (val) ? of_property_read_##type##_array((node), (propname), (val), (nval)) \ : of_property_count_elems_of_size((node), (propname), sizeof(type)) @@ -274,3 +419,207 @@ int device_property_read_string_array(struct device *dev, const char *propname, DEV_PROP_STRING, val, nval); } EXPORT_SYMBOL_GPL(device_property_read_string_array); + +#define FWNODE_PROPERTY_READ_ARRAY(_fwnode_, _propname_, _type_, _proptype_, _val_, _nval_) \ +({ \ + int _ret_; \ + if (is_of_node(_fwnode_)) \ + _ret_ = OF_DEV_PROP_READ_ARRAY(of_node(_fwnode_), _propname_, \ + _type_, _val_, _nval_); \ + else if (is_acpi_node(_fwnode_)) \ + _ret_ = acpi_dev_prop_read_array(acpi_node(_fwnode_), \ + _propname_, _proptype_, \ + _val_, _nval_); \ + else \ + _ret_ = -ENXIO; \ + _ret_; \ +}) + +/** + * fwnode_property_read_u8_array - return a u8 array property of firmware node + * @fwnode: Firmware node to get the property of + * @propname: Name of the property + * @val: The values are stored here + * @nval: Size of the @val array + * + * Read an array of u8 properties with @propname from @fwnode and stores them to + * @val if found. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO if the property is not an array of numbers, + * %-EOVERFLOW if the size of the property is not as expected, + * %-ENXIO if no suitable firmware interface is present. + */ +int fwnode_property_read_u8_array(struct fwnode_handle *fwnode, + const char *propname, u8 *val, size_t nval) +{ + return FWNODE_PROPERTY_READ_ARRAY(fwnode, propname, u8, DEV_PROP_U8, + val, nval); +} +EXPORT_SYMBOL_GPL(fwnode_property_read_u8_array); + +/** + * fwnode_property_read_u16_array - return a u16 array property of firmware node + * @fwnode: Firmware node to get the property of + * @propname: Name of the property + * @val: The values are stored here + * @nval: Size of the @val array + * + * Read an array of u16 properties with @propname from @fwnode and store them to + * @val if found. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO if the property is not an array of numbers, + * %-EOVERFLOW if the size of the property is not as expected, + * %-ENXIO if no suitable firmware interface is present. + */ +int fwnode_property_read_u16_array(struct fwnode_handle *fwnode, + const char *propname, u16 *val, size_t nval) +{ + return FWNODE_PROPERTY_READ_ARRAY(fwnode, propname, u16, DEV_PROP_U16, + val, nval); +} +EXPORT_SYMBOL_GPL(fwnode_property_read_u16_array); + +/** + * fwnode_property_read_u32_array - return a u32 array property of firmware node + * @fwnode: Firmware node to get the property of + * @propname: Name of the property + * @val: The values are stored here + * @nval: Size of the @val array + * + * Read an array of u32 properties with @propname from @fwnode store them to + * @val if found. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO if the property is not an array of numbers, + * %-EOVERFLOW if the size of the property is not as expected, + * %-ENXIO if no suitable firmware interface is present. + */ +int fwnode_property_read_u32_array(struct fwnode_handle *fwnode, + const char *propname, u32 *val, size_t nval) +{ + return FWNODE_PROPERTY_READ_ARRAY(fwnode, propname, u32, DEV_PROP_U32, + val, nval); +} +EXPORT_SYMBOL_GPL(fwnode_property_read_u32_array); + +/** + * fwnode_property_read_u64_array - return a u64 array property firmware node + * @fwnode: Firmware node to get the property of + * @propname: Name of the property + * @val: The values are stored here + * @nval: Size of the @val array + * + * Read an array of u64 properties with @propname from @fwnode and store them to + * @val if found. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO if the property is not an array of numbers, + * %-EOVERFLOW if the size of the property is not as expected, + * %-ENXIO if no suitable firmware interface is present. + */ +int fwnode_property_read_u64_array(struct fwnode_handle *fwnode, + const char *propname, u64 *val, size_t nval) +{ + return FWNODE_PROPERTY_READ_ARRAY(fwnode, propname, u64, DEV_PROP_U64, + val, nval); +} +EXPORT_SYMBOL_GPL(fwnode_property_read_u64_array); + +/** + * fwnode_property_read_string_array - return string array property of a node + * @fwnode: Firmware node to get the property of + * @propname: Name of the property + * @val: The values are stored here + * @nval: Size of the @val array + * + * Read an string list property @propname from the given firmware node and store + * them to @val if found. + * + * Return: %0 if the property was found (success), + * %-EINVAL if given arguments are not valid, + * %-ENODATA if the property does not have a value, + * %-EPROTO if the property is not an array of strings, + * %-EOVERFLOW if the size of the property is not as expected, + * %-ENXIO if no suitable firmware interface is present. + */ +int fwnode_property_read_string_array(struct fwnode_handle *fwnode, + const char *propname, char **val, + size_t nval) +{ + if (is_of_node(fwnode)) + return of_property_read_string_array(of_node(fwnode), propname, + val, nval); + else if (is_acpi_node(fwnode)) + return acpi_dev_prop_read_array(acpi_node(fwnode), propname, + DEV_PROP_STRING, val, nval); + + return -ENXIO; +} +EXPORT_SYMBOL_GPL(fwnode_property_read_string_array); + + +/** + * device_get_next_child_node - Return the next child node handle for a device + * @dev: Device to find the next child node for. + * @child: Handle to one of the device's child nodes or a null handle. + */ +struct fwnode_handle *device_get_next_child_node(struct device *dev, + struct fwnode_handle *child) +{ + if (IS_ENABLED(CONFIG_OF) && dev->of_node) { + struct device_node *node; + + node = of_get_next_available_child(dev->of_node, of_node(child)); + if (node) + return &node->fwnode; + } else if (ACPI_COMPANION(dev)) { + struct acpi_device *node; + + node = acpi_get_next_child(dev, acpi_node(child)); + if (node) + return acpi_fwnode_handle(node); + } + return NULL; +} +EXPORT_SYMBOL_GPL(device_get_next_child_node); + +/** + * fwnode_handle_put - Drop reference to a device node + * @fwnode: Pointer to the device node to drop the reference to. + * + * This has to be used when terminating device_for_each_child_node() iteration + * with break or return to prevent stale device node references from being left + * behind. + */ +void fwnode_handle_put(struct fwnode_handle *fwnode) +{ + if (is_of_node(fwnode)) + of_node_put(of_node(fwnode)); +} +EXPORT_SYMBOL_GPL(fwnode_handle_put); + +/** + * device_get_child_node_count - return the number of child nodes for device + * @dev: Device to cound the child nodes for + */ +unsigned int device_get_child_node_count(struct device *dev) +{ + struct fwnode_handle *child; + unsigned int count = 0; + + device_for_each_child_node(dev, child) + count++; + + return count; +} +EXPORT_SYMBOL_GPL(device_get_child_node_count); diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index f59cbf8..a361f43 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -27,6 +27,7 @@ #define __ACPI_BUS_H__
#include <linux/device.h> +#include <linux/property.h>
/* TBD: Make dynamic */ #define ACPI_MAX_HANDLES 10 @@ -348,6 +349,7 @@ struct acpi_device_data { struct acpi_device { int device_type; acpi_handle handle; /* no handle for fixed hardware */ + struct fwnode_handle fwnode; struct acpi_device *parent; struct list_head children; struct list_head node; @@ -372,6 +374,21 @@ struct acpi_device { void (*remove)(struct acpi_device *); };
+static inline bool is_acpi_node(struct fwnode_handle *fwnode) +{ + return fwnode && fwnode->type == FWNODE_ACPI; +} + +static inline struct acpi_device *acpi_node(struct fwnode_handle *fwnode) +{ + return fwnode ? container_of(fwnode, struct acpi_device, fwnode) : NULL; +} + +static inline struct fwnode_handle *acpi_fwnode_handle(struct acpi_device *adev) +{ + return &adev->fwnode; +} + static inline void *acpi_driver_data(struct acpi_device *d) { return d->driver_data; diff --git a/include/linux/acpi.h b/include/linux/acpi.h index dc3f75f..a979cec 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -445,6 +445,23 @@ struct platform_device *acpi_create_platform_device(struct acpi_device *); #define ACPI_COMPANION_SET(dev, adev) do { } while (0) #define ACPI_HANDLE(dev) (NULL)
+struct fwnode_handle; + +static inline bool is_acpi_node(struct fwnode_handle *fwnode) +{ + return false; +} + +static inline struct acpi_device *acpi_node(struct fwnode_handle *fwnode) +{ + return NULL; +} + +static inline struct fwnode_handle *acpi_fwnode_handle(struct acpi_device *adev) +{ + return NULL; +} + static inline const char *acpi_dev_name(struct acpi_device *adev) { return NULL; @@ -687,6 +704,9 @@ int acpi_dev_prop_read(struct acpi_device *adev, const char *propname, int acpi_dev_prop_read_array(struct acpi_device *adev, const char *propname, enum dev_prop_type proptype, void *val, size_t nval); + +struct acpi_device *acpi_get_next_child(struct device *dev, + struct acpi_device *child); #else static inline int acpi_dev_get_property(struct acpi_device *adev, const char *name, acpi_object_type type, @@ -730,6 +750,12 @@ static inline int acpi_dev_prop_read_array(struct acpi_device *adev, return -ENXIO; }
+static inline struct acpi_device *acpi_get_next_child(struct device *dev, + struct acpi_device *child) +{ + return NULL; +} + #endif
#endif /*_LINUX_ACPI_H*/ diff --git a/include/linux/of.h b/include/linux/of.h index 25efe42..ca873e9 100644 --- a/include/linux/of.h +++ b/include/linux/of.h @@ -50,6 +50,7 @@ struct device_node { const char *type; phandle phandle; const char *full_name; + struct fwnode_handle fwnode;
struct property *properties; struct property *deadprops; /* removed properties */ @@ -80,6 +81,7 @@ extern struct kobj_type of_node_ktype; static inline void of_node_init(struct device_node *node) { kobject_init(&node->kobj, &of_node_ktype); + node->fwnode.type = FWNODE_OF; }
/* true when node is initialized */ @@ -115,6 +117,16 @@ extern struct device_node *of_aliases; extern struct device_node *of_stdout; extern raw_spinlock_t devtree_lock;
+static inline bool is_of_node(struct fwnode_handle *fwnode) +{ + return fwnode && fwnode->type == FWNODE_OF; +} + +static inline struct device_node *of_node(struct fwnode_handle *fwnode) +{ + return fwnode ? container_of(fwnode, struct device_node, fwnode) : NULL; +} + static inline bool of_have_populated_dt(void) { return of_allnodes != NULL; @@ -365,6 +377,16 @@ bool of_console_check(struct device_node *dn, char *name, int index);
#else /* CONFIG_OF */
+static inline bool is_of_node(struct fwnode_handle *fwnode) +{ + return false; +} + +static inline struct device_node *of_node(struct fwnode_handle *fwnode) +{ + return NULL; +} + static inline const char* of_node_full_name(const struct device_node *np) { return "<no-node>"; diff --git a/include/linux/property.h b/include/linux/property.h index 7a3555f..3231fdd 100644 --- a/include/linux/property.h +++ b/include/linux/property.h @@ -44,10 +44,64 @@ int device_property_read_u64_array(struct device *dev, const char *propname, int device_property_read_string_array(struct device *dev, const char *propname, char **val, size_t nval);
+enum fwnode_type { + FWNODE_INVALID = 0, + FWNODE_OF, + FWNODE_ACPI, +}; + +struct fwnode_handle { + enum fwnode_type type; +}; + +bool fwnode_property_present(struct fwnode_handle *fwnode, const char *propname); +int fwnode_property_read_u8(struct fwnode_handle *fwnode, const char *propname, + u8 *val); +int fwnode_property_read_u16(struct fwnode_handle *fwnode, const char *propname, + u16 *val); +int fwnode_property_read_u32(struct fwnode_handle *fwnode, const char *propname, + u32 *val); +int fwnode_property_read_u64(struct fwnode_handle *fwnode, const char *propname, + u64 *val); +int fwnode_property_read_string(struct fwnode_handle *fwnode, + const char *propname, const char **val); +int fwnode_property_read_u8_array(struct fwnode_handle *fwnode, + const char *propname, u8 *val, + size_t nval); +int fwnode_property_read_u16_array(struct fwnode_handle *fwnode, + const char *propname, u16 *val, + size_t nval); +int fwnode_property_read_u32_array(struct fwnode_handle *fwnode, + const char *propname, u32 *val, + size_t nval); +int fwnode_property_read_u64_array(struct fwnode_handle *fwnode, + const char *propname, u64 *val, + size_t nval); +int fwnode_property_read_string_array(struct fwnode_handle *fwnode, + const char *propname, char **val, + size_t nval); + +struct fwnode_handle *device_get_next_child_node(struct device *dev, + struct fwnode_handle *child); + +#define device_for_each_child_node(dev, child) \ + for (child = device_get_next_child_node(dev, NULL); child; \ + child = device_get_next_child_node(dev, child)) + +void fwnode_handle_put(struct fwnode_handle *fwnode); + +unsigned int device_get_child_node_count(struct device *dev); + static inline bool device_property_read_bool(struct device *dev, const char *propname) { return device_property_present(dev, propname); }
+static inline bool fwnode_property_read_bool(struct fwnode_handle *fwnode, + const char *propname) +{ + return fwnode_property_present(fwnode, propname); +} + #endif /* _LINUX_PROPERTY_H_ */
On Mon, Nov 10, 2014 at 8:47 PM, al.stone@linaro.org wrote:
+int fwnode_property_read_string_array(struct fwnode_handle *fwnode,
const char *propname, char **val,
size_t nval)
+{
if (is_of_node(fwnode))
return of_property_read_string_array(of_node(fwnode), propname,
val, nval);
I figured someone would have noticed this already, but I don't see any posts on the subject. The above code causes this build warning:
drivers/base/property.c: In function ‘fwnode_property_read_string_array’: drivers/base/property.c:561:12: warning: passing argument 3 of ‘of_property_read_string_array’ from incompatible pointer type [enabled by default] val, nval); ^ In file included from drivers/base/property.c:16:0: include/linux/of.h:748:19: note: expected ‘const char **’ but argument is of type ‘char **’ static inline int of_property_read_string_array(struct device_node *np, ^
The 'val' parameter is "char **", but of_property_read_string_array() has this prototype:
static inline int of_property_read_string_array(struct device_node *np, const char *propname, const char **out_strs, size_t sz)
Hi Timur,
On 2015年01月10日 06:08, Timur Tabi wrote:
On Mon, Nov 10, 2014 at 8:47 PM, al.stone@linaro.org wrote:
+int fwnode_property_read_string_array(struct fwnode_handle *fwnode,
const char *propname, char **val,
size_t nval)
+{
if (is_of_node(fwnode))
return of_property_read_string_array(of_node(fwnode), propname,
val, nval);
I figured someone would have noticed this already, but I don't see any posts on the subject. The above code causes this build warning:
drivers/base/property.c: In function ‘fwnode_property_read_string_array’: drivers/base/property.c:561:12: warning: passing argument 3 of ‘of_property_read_string_array’ from incompatible pointer type [enabled by default] val, nval); ^ In file included from drivers/base/property.c:16:0: include/linux/of.h:748:19: note: expected ‘const char **’ but argument is of type ‘char **’ static inline int of_property_read_string_array(struct device_node *np, ^
The 'val' parameter is "char **", but of_property_read_string_array() has this prototype:
static inline int of_property_read_string_array(struct device_node *np, const char *propname, const char **out_strs, size_t sz)
code in mainline (3.19) already fixed this problem:
/** * fwnode_property_read_string_array - return string array property of a node * @fwnode: Firmware node to get the property of * @propname: Name of the property * @val: The values are stored here * @nval: Size of the @val array * * Read an string list property @propname from the given firmware node and store * them to @val if found. * * Return: %0 if the property was found (success), * %-EINVAL if given arguments are not valid, * %-ENODATA if the property does not have a value, * %-EPROTO if the property is not an array of strings, * %-EOVERFLOW if the size of the property is not as expected, * %-ENXIO if no suitable firmware interface is present. */ int fwnode_property_read_string_array(struct fwnode_handle *fwnode, const char *propname, const char **val, size_t nval)
Thanks Hanjun
On 01/11/2015 09:45 PM, Hanjun Guo wrote:
code in mainline (3.19) already fixed this problem:
Weird, I could have sworn I checked mainline and didn't see this fix, but I checked again, and there it is. Oh well, sorry about the noise.
From: Mark Salter msalter@redhat.com
--- drivers/net/ethernet/apm/xgene/xgene_enet_hw.c | 77 ++++++++++++++++++++---- drivers/net/ethernet/apm/xgene/xgene_enet_main.c | 68 ++++++++++++++++++--- drivers/net/ethernet/apm/xgene/xgene_enet_main.h | 1 + 3 files changed, 126 insertions(+), 20 deletions(-)
diff --git a/drivers/net/ethernet/apm/xgene/xgene_enet_hw.c b/drivers/net/ethernet/apm/xgene/xgene_enet_hw.c index 63ea194..bb059b4 100644 --- a/drivers/net/ethernet/apm/xgene/xgene_enet_hw.c +++ b/drivers/net/ethernet/apm/xgene/xgene_enet_hw.c @@ -579,9 +579,11 @@ static void xgene_enet_reset(struct xgene_enet_pdata *pdata) { u32 val;
- clk_prepare_enable(pdata->clk); - clk_disable_unprepare(pdata->clk); - clk_prepare_enable(pdata->clk); + if (pdata->clk) { + clk_prepare_enable(pdata->clk); + clk_disable_unprepare(pdata->clk); + clk_prepare_enable(pdata->clk); + } xgene_enet_ecc_init(pdata); xgene_enet_config_ring_if_assoc(pdata);
@@ -647,15 +649,20 @@ static int xgene_enet_phy_connect(struct net_device *ndev) struct phy_device *phy_dev; struct device *dev = &pdata->pdev->dev;
- phy_np = of_parse_phandle(dev->of_node, "phy-handle", 0); - if (!phy_np) { - netdev_dbg(ndev, "No phy-handle found\n"); - return -ENODEV; + if (dev->of_node) { + phy_np = of_parse_phandle(dev->of_node, "phy-handle", 0); + if (!phy_np) { + netdev_dbg(ndev, "No phy-handle found in DT\n"); + return -ENODEV; + } + pdata->phy_dev = of_phy_find_device(phy_np); }
- phy_dev = of_phy_connect(ndev, phy_np, &xgene_enet_adjust_link, - 0, pdata->phy_mode); - if (!phy_dev) { + phy_dev = pdata->phy_dev; + + if (phy_dev == NULL || + phy_connect_direct(ndev, phy_dev, &xgene_enet_adjust_link, + pdata->phy_mode)) { netdev_err(ndev, "Could not connect to PHY\n"); return -ENODEV; } @@ -665,11 +672,52 @@ static int xgene_enet_phy_connect(struct net_device *ndev) ~SUPPORTED_100baseT_Half & ~SUPPORTED_1000baseT_Half; phy_dev->advertising = phy_dev->supported; - pdata->phy_dev = phy_dev;
return 0; }
+#ifdef CONFIG_ACPI +static int xgene_acpi_mdiobus_register(struct xgene_enet_pdata *pdata, + struct mii_bus *mdio) +{ + struct device *dev = &pdata->pdev->dev; + struct phy_device *phy; + int i, ret; + u32 phy_id; + + /* Mask out all PHYs from auto probing. */ + mdio->phy_mask = ~0; + + /* Clear all the IRQ properties */ + if (mdio->irq) + for (i = 0; i < PHY_MAX_ADDR; i++) + mdio->irq[i] = PHY_POLL; + + /* Register the MDIO bus */ + ret = mdiobus_register(mdio); + if (ret) + return ret; + + ret = device_property_read_u32(dev, "phy-channel", &phy_id); + if (ret) + return -EINVAL; + + phy = get_phy_device(mdio, phy_id, true); + if (!phy || IS_ERR(phy)) + return -EIO; + + ret = phy_device_register(phy); + if (ret) + phy_device_free(phy); + else + pdata->phy_dev = phy; + + return ret; +} +#else +#define xgene_acpi_mdiobus_register(a, b) -1 +#endif + int xgene_enet_mdio_config(struct xgene_enet_pdata *pdata) { struct net_device *ndev = pdata->ndev; @@ -686,7 +734,7 @@ int xgene_enet_mdio_config(struct xgene_enet_pdata *pdata) } }
- if (!mdio_np) { + if (dev->of_node && !mdio_np) { netdev_dbg(ndev, "No mdio node in the dts\n"); return -ENXIO; } @@ -704,7 +752,10 @@ int xgene_enet_mdio_config(struct xgene_enet_pdata *pdata) mdio_bus->priv = pdata; mdio_bus->parent = &ndev->dev;
- ret = of_mdiobus_register(mdio_bus, mdio_np); + if (dev->of_node) + ret = of_mdiobus_register(mdio_bus, mdio_np); + else + ret = xgene_acpi_mdiobus_register(pdata, mdio_bus); if (ret) { netdev_err(ndev, "Failed to register MDIO bus\n"); mdiobus_free(mdio_bus); diff --git a/drivers/net/ethernet/apm/xgene/xgene_enet_main.c b/drivers/net/ethernet/apm/xgene/xgene_enet_main.c index 3c208cc..6370ff4 100644 --- a/drivers/net/ethernet/apm/xgene/xgene_enet_main.c +++ b/drivers/net/ethernet/apm/xgene/xgene_enet_main.c @@ -746,6 +746,42 @@ static const struct net_device_ops xgene_ndev_ops = { .ndo_set_mac_address = xgene_enet_set_mac_address, };
+#ifdef CONFIG_ACPI +static int acpi_get_mac_address(struct device *dev, + unsigned char *addr) +{ + int ret; + + ret = device_property_read_u8_array(dev, "mac-address", addr, 6); + if (ret) + return 0; + + return 6; +} + +static int acpi_get_phy_mode(struct device *dev) +{ + int i, ret, phy_mode; + char *modestr; + + ret = device_property_read_string(dev, "phy-mode", &modestr); + if (ret) + return -1; + + phy_mode = -1; + for (i = 0; i < PHY_INTERFACE_MODE_MAX; i++) { + if (!strcasecmp(modestr, phy_modes(i))) { + phy_mode = i; + break; + } + } + return phy_mode; +} +#else +#define acpi_get_mac_address(a, b, c) 0 +#define acpi_get_phy_mode(a) -1 +#endif + static int xgene_enet_get_resources(struct xgene_enet_pdata *pdata) { struct platform_device *pdev; @@ -761,6 +797,8 @@ static int xgene_enet_get_resources(struct xgene_enet_pdata *pdata) ndev = pdata->ndev;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "enet_csr"); + if (!res) + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(dev, "Resource enet_csr not defined\n"); return -ENODEV; @@ -772,6 +810,8 @@ static int xgene_enet_get_resources(struct xgene_enet_pdata *pdata) }
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ring_csr"); + if (!res) + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); if (!res) { dev_err(dev, "Resource ring_csr not defined\n"); return -ENODEV; @@ -783,6 +823,8 @@ static int xgene_enet_get_resources(struct xgene_enet_pdata *pdata) }
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ring_cmd"); + if (!res) + res = platform_get_resource(pdev, IORESOURCE_MEM, 2); if (!res) { dev_err(dev, "Resource ring_cmd not defined\n"); return -ENODEV; @@ -804,11 +846,13 @@ static int xgene_enet_get_resources(struct xgene_enet_pdata *pdata) mac = of_get_mac_address(dev->of_node); if (mac) memcpy(ndev->dev_addr, mac, ndev->addr_len); - else + else if (!acpi_get_mac_address(dev, ndev->dev_addr)) eth_hw_addr_random(ndev); memcpy(ndev->perm_addr, ndev->dev_addr, ndev->addr_len);
pdata->phy_mode = of_get_phy_mode(pdev->dev.of_node); + if (pdata->phy_mode < 0) + pdata->phy_mode = acpi_get_phy_mode(dev); if (pdata->phy_mode < 0) { dev_err(dev, "Unable to get phy-connection-type\n"); return pdata->phy_mode; @@ -821,11 +865,12 @@ static int xgene_enet_get_resources(struct xgene_enet_pdata *pdata) }
pdata->clk = devm_clk_get(&pdev->dev, NULL); - ret = IS_ERR(pdata->clk); if (IS_ERR(pdata->clk)) { - dev_err(&pdev->dev, "can't get clock\n"); - ret = PTR_ERR(pdata->clk); - return ret; + /* + * Not necessarily an error. Firmware may have + * set up the clock already. + */ + pdata->clk = NULL; }
base_addr = pdata->base_addr; @@ -873,7 +918,7 @@ static int xgene_enet_init_hw(struct xgene_enet_pdata *pdata) pdata->port_ops->cle_bypass(pdata, dst_ring_num, buf_pool->id); pdata->mac_ops->init(pdata);
- return ret; + return 0; }
static void xgene_enet_setup_ops(struct xgene_enet_pdata *pdata) @@ -934,7 +979,7 @@ static int xgene_enet_probe(struct platform_device *pdev) goto err; }
- ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); + ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(64)); if (ret) { netdev_err(ndev, "No usable DMA configuration\n"); goto err; @@ -981,6 +1026,14 @@ static int xgene_enet_remove(struct platform_device *pdev) return 0; }
+#ifdef CONFIG_ACPI +static const struct acpi_device_id xgene_enet_acpi_match[] = { + { "APMC0D05", }, + { } +}; +MODULE_DEVICE_TABLE(acpi, xgene_enet_acpi_match); +#endif + static struct of_device_id xgene_enet_match[] = { {.compatible = "apm,xgene-enet",}, {}, @@ -992,6 +1045,7 @@ static struct platform_driver xgene_enet_driver = { .driver = { .name = "xgene-enet", .of_match_table = xgene_enet_match, + .acpi_match_table = ACPI_PTR(xgene_enet_acpi_match), }, .probe = xgene_enet_probe, .remove = xgene_enet_remove, diff --git a/drivers/net/ethernet/apm/xgene/xgene_enet_main.h b/drivers/net/ethernet/apm/xgene/xgene_enet_main.h index 874e5a0..8b7e2cf 100644 --- a/drivers/net/ethernet/apm/xgene/xgene_enet_main.h +++ b/drivers/net/ethernet/apm/xgene/xgene_enet_main.h @@ -31,6 +31,7 @@ #include <linux/prefetch.h> #include <linux/if_vlan.h> #include <linux/phy.h> +#include <linux/acpi.h> #include "xgene_enet_hw.h"
#define XGENE_DRV_VERSION "v1.0"
On Monday 10 November 2014 19:47:06 al.stone@linaro.org wrote:
- phy_dev = of_phy_connect(ndev, phy_np, &xgene_enet_adjust_link,
0, pdata->phy_mode);
- if (!phy_dev) {
- phy_dev = pdata->phy_dev;
- if (phy_dev == NULL ||
phy_connect_direct(ndev, phy_dev, &xgene_enet_adjust_link,
netdev_err(ndev, "Could not connect to PHY\n"); return -ENODEV; }pdata->phy_mode)) {
This looks problematic to me: the existing driver handles arbitrary PHYs, which I assume was intentional, while the ACPI code would only work with the built-in PHY.
This will break as soon as anyone uses this chip with an external PHY.
- ret = device_property_read_u32(dev, "phy-channel", &phy_id);
- if (ret)
return -EINVAL;
Undocumented property?
There is also still the open question on whether it's appropriate to use DT properties on ARM64 servers with ACPI or not.
diff --git a/drivers/net/ethernet/apm/xgene/xgene_enet_main.c b/drivers/net/ethernet/apm/xgene/xgene_enet_main.c index 3c208cc..6370ff4 100644 --- a/drivers/net/ethernet/apm/xgene/xgene_enet_main.c +++ b/drivers/net/ethernet/apm/xgene/xgene_enet_main.c @@ -746,6 +746,42 @@ static const struct net_device_ops xgene_ndev_ops = { .ndo_set_mac_address = xgene_enet_set_mac_address, }; +#ifdef CONFIG_ACPI +static int acpi_get_mac_address(struct device *dev,
unsigned char *addr)
+{
- int ret;
- ret = device_property_read_u8_array(dev, "mac-address", addr, 6);
- if (ret)
return 0;
- return 6;
+}
If you need this, please submit a patch to generalize the of_get_mac_address helper the way that the other properties are handled.
+static int acpi_get_phy_mode(struct device *dev) +{
- int i, ret, phy_mode;
- char *modestr;
- ret = device_property_read_string(dev, "phy-mode", &modestr);
- if (ret)
return -1;
- phy_mode = -1;
- for (i = 0; i < PHY_INTERFACE_MODE_MAX; i++) {
if (!strcasecmp(modestr, phy_modes(i))) {
phy_mode = i;
break;
}
- }
- return phy_mode;
+} +#else +#define acpi_get_mac_address(a, b, c) 0 +#define acpi_get_phy_mode(a) -1 +#endif
Same here with of_get_phy_mode
static int xgene_enet_get_resources(struct xgene_enet_pdata *pdata) { struct platform_device *pdev; @@ -761,6 +797,8 @@ static int xgene_enet_get_resources(struct xgene_enet_pdata *pdata) ndev = pdata->ndev; res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "enet_csr");
- if (!res)
if (!res) { dev_err(dev, "Resource enet_csr not defined\n"); return -ENODEV;res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
These should really be merged into a single call, either by relying on the order on DT, or by naming the register resources on ACPI.
static void xgene_enet_setup_ops(struct xgene_enet_pdata *pdata) @@ -934,7 +979,7 @@ static int xgene_enet_probe(struct platform_device *pdev) goto err; }
- ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
- ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(64)); if (ret) { netdev_err(ndev, "No usable DMA configuration\n"); goto err;
No, you have to fix the ACPI code to set the dma mask pointer correctly for DMA capable devices. dma_coerce_mask_and_coherent is not acceptable for new code.
Arnd
From: Mark Salter msalter@redhat.com
ACPI 5.1 adds a _CCA object to indicate memory coherency of a bus master device. It is an integer with zero meaning non-coherent and one meaning coherent. This attribute may be inherited from a parent device. It may also be missing entirely, in which case, an architecture-specific default is assumed.
This patch adds a utility function to parse a device handle (and its parents) for a _CCA object and return the coherency attribute if found.
Signed-off-by: Mark Salter msalter@redhat.com --- drivers/acpi/utils.c | 26 ++++++++++++++++++++++++++ include/acpi/acpi_bus.h | 2 ++ 2 files changed, 28 insertions(+)
diff --git a/drivers/acpi/utils.c b/drivers/acpi/utils.c index 371ac12..af325a7 100644 --- a/drivers/acpi/utils.c +++ b/drivers/acpi/utils.c @@ -723,3 +723,29 @@ bool acpi_check_dsm(acpi_handle handle, const u8 *uuid, int rev, u64 funcs) return false; } EXPORT_SYMBOL(acpi_check_dsm); + +/** + * acpi_check_coherency - check for memory coherency of a device + * @handle: ACPI device handle + * @val: Pointer to returned value + * + * Search a device and its parents for a _CCA method and return + * its value. + */ +acpi_status acpi_check_coherency(acpi_handle handle, int *val) +{ + unsigned long long data; + acpi_status status; + + do { + status = acpi_evaluate_integer(handle, "_CCA", NULL, &data); + if (!ACPI_FAILURE(status)) { + *val = data; + break; + } + status = acpi_get_parent(handle, &handle); + } while (!ACPI_FAILURE(status)); + + return status; +} +EXPORT_SYMBOL(acpi_check_coherency); diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index a361f43..0d4ce14 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -69,6 +69,8 @@ bool acpi_check_dsm(acpi_handle handle, const u8 *uuid, int rev, u64 funcs); union acpi_object *acpi_evaluate_dsm(acpi_handle handle, const u8 *uuid, int rev, int func, union acpi_object *argv4);
+acpi_status acpi_check_coherency(acpi_handle handle, int *val); + static inline union acpi_object * acpi_evaluate_dsm_typed(acpi_handle handle, const u8 *uuid, int rev, int func, union acpi_object *argv4, acpi_object_type type)
From: Mark Salter msalter@redhat.com
Commit 2189064795dc3fb4101e5:
arm64: Implement set_arch_dma_coherent_ops() to replace bus notifiers
removed the bus notifiers from dma-mapping.c. This patch adds the notifier back for ACPI and PCI devices until a better permanent solution is worked out.
Signed-off-by: Mark Salter msalter@redhat.com --- arch/arm64/mm/dma-mapping.c | 103 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+)
diff --git a/arch/arm64/mm/dma-mapping.c b/arch/arm64/mm/dma-mapping.c index d920942..fda70ab 100644 --- a/arch/arm64/mm/dma-mapping.c +++ b/arch/arm64/mm/dma-mapping.c @@ -23,8 +23,14 @@ #include <linux/genalloc.h> #include <linux/dma-mapping.h> #include <linux/dma-contiguous.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> #include <linux/vmalloc.h> #include <linux/swiotlb.h> +#include <linux/amba/bus.h> +#include <linux/acpi.h> +#include <linux/pci.h>
#include <asm/cacheflush.h>
@@ -423,10 +429,107 @@ out: return -ENOMEM; }
+#ifdef CONFIG_PCI +static void arm64_of_set_dma_ops(void *_dev) +{ + struct device *dev = _dev; + + /* + * PCI devices won't have an ACPI handle but the bridge will. + * Search up the device chain until we find an of_node + * to check. + */ + while (dev) { + if (dev->of_node) { + if (of_dma_is_coherent(dev->of_node)) + set_dma_ops(_dev, &coherent_swiotlb_dma_ops); + break; + } + dev = dev->parent; + } +} +#else +static inline arm64_of_set_dma_ops(void *_dev) {} +#endif + + +#ifdef CONFIG_ACPI +static void arm64_acpi_set_dma_ops(void *_dev) +{ + struct device *dev = _dev; + + /* + * Kernel defaults to noncoherent ops but ACPI 5.1 spec says arm64 + * defaults to coherent. Set coherent ops if _CCA not found or _CCA + * found and non-zero. + * + * PCI devices won't have an of_node but the bridge will. + * Search up the device chain until we find an ACPI handle + * to check. + */ + while (dev) { + if (ACPI_HANDLE(dev)) { + acpi_status status; + int coherent; + status = acpi_check_coherency(ACPI_HANDLE(dev), + &coherent); + if (ACPI_FAILURE(status) || coherent) + set_dma_ops(dev, &coherent_swiotlb_dma_ops); + break; + } + dev = dev->parent; + } +} +#else +static inline arm64_acpi_set_dma_ops(void *_dev) {} +#endif + +static int dma_bus_notifier(struct notifier_block *nb, + unsigned long event, void *_dev) +{ + if (event != BUS_NOTIFY_ADD_DEVICE) + return NOTIFY_DONE; + + if (acpi_disabled) + arm64_of_set_dma_ops(_dev); + else + arm64_acpi_set_dma_ops(_dev); + + return NOTIFY_OK; +} + +#ifdef CONFIG_ACPI +static struct notifier_block platform_bus_nb = { + .notifier_call = dma_bus_notifier, +}; + +static struct notifier_block amba_bus_nb = { + .notifier_call = dma_bus_notifier, +}; +#endif + +#ifdef CONFIG_PCI +static struct notifier_block pci_bus_nb = { + .notifier_call = dma_bus_notifier, +}; +#endif + static int __init swiotlb_late_init(void) { size_t swiotlb_size = min(SZ_64M, MAX_ORDER_NR_PAGES << PAGE_SHIFT);
+ /* + * These must be registered before of_platform_populate(). + */ +#ifdef CONFIG_ACPI + bus_register_notifier(&platform_bus_type, &platform_bus_nb); + bus_register_notifier(&amba_bustype, &amba_bus_nb); +#endif + +#ifdef CONFIG_PCI + bus_register_notifier(&pci_bus_type, &pci_bus_nb); +#endif + dma_ops = &noncoherent_swiotlb_dma_ops;
return swiotlb_late_init_with_default_size(swiotlb_size);
From: Al Stone ahs3@redhat.com
Arm allows for two possible architectural clock sources. One memory mapped and the other coprocessor based. If both timers exist, then the driver waits for both to be probed before registering a clocksource.
Commit c387f07e6205 ("clocksource: arm_arch_timer: Discard unavailable timers correctly") attempted to fix a hang occurring when one of the two possible timers had a device node, but was disabled. In that case, the second probe would never occur and the system would hang without a clocksource being registered.
Unfortunately, incorrect logic in that commit made things worse such that a hang would occur unless both timers had a device node and were enabled. This patch fixes the logic so that we don't wait to probe a second timer unless it exists and is enabled.
Signed-off-by: Mark Salter msalter@redhat.com --- drivers/clocksource/arm_arch_timer.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/drivers/clocksource/arm_arch_timer.c b/drivers/clocksource/arm_arch_timer.c index 7227ab2..394e06c 100644 --- a/drivers/clocksource/arm_arch_timer.c +++ b/drivers/clocksource/arm_arch_timer.c @@ -668,10 +668,11 @@ arch_timer_probed(int type, const struct of_device_id *matches) bool probed = true;
dn = of_find_matching_node(NULL, matches); - if (dn && of_device_is_available(dn) && !(arch_timers_present & type)) - probed = false; - of_node_put(dn); - + if (dn) { + if (of_device_is_available(dn) && !(arch_timers_present & type)) + probed = false; + of_node_put(dn); + } return probed; }
On 11/11/14 02:47, al.stone@linaro.org wrote:
From: Al Stone ahs3@redhat.com
Arm allows for two possible architectural clock sources. One memory mapped and the other coprocessor based. If both timers exist, then the driver waits for both to be probed before registering a clocksource.
Commit c387f07e6205 ("clocksource: arm_arch_timer: Discard unavailable timers correctly") attempted to fix a hang occurring when one of the two possible timers had a device node, but was disabled. In that case, the second probe would never occur and the system would hang without a clocksource being registered.
Unfortunately, incorrect logic in that commit made things worse such that a hang would occur unless both timers had a device node and were enabled. This patch fixes the logic so that we don't wait to probe a second timer unless it exists and is enabled.
Sorry I introduced this bug and Marc Zyngier's fix is already there in v3.18-rc3, so you can drop this once you rebase on or after v3.18-rc3
Regards, Sudeep
From: Mark Salter msalter@redhat.com
The current code provides placeholder weak functions for for raw_pci_read() and raw_pci_write(). This patch creates a global 'raw_pci_ops' variable which points to a struct containing read and write ops. The weak attribute is removed from raw_pci_read() and raw_pci_write() which will use the ops provided by the raw_pci_ops pointer.
Signed-off-by: Mark Salter msalter@redhat.com --- arch/arm64/include/asm/pci.h | 9 +++++++++ arch/arm64/pci/pci.c | 20 ++++++++++++++------ 2 files changed, 23 insertions(+), 6 deletions(-)
diff --git a/arch/arm64/include/asm/pci.h b/arch/arm64/include/asm/pci.h index fded096..9e23df3 100644 --- a/arch/arm64/include/asm/pci.h +++ b/arch/arm64/include/asm/pci.h @@ -39,5 +39,14 @@ static inline int pci_proc_domain(struct pci_bus *bus) } #endif /* CONFIG_PCI */
+struct pci_raw_ops { + int (*read)(unsigned int domain, unsigned int bus, unsigned int devfn, + int reg, int len, u32 *val); + int (*write)(unsigned int domain, unsigned int bus, unsigned int devfn, + int reg, int len, u32 val); +}; + +extern const struct pci_raw_ops *raw_pci_ops; + #endif /* __KERNEL__ */ #endif /* __ASM_PCI_H */ diff --git a/arch/arm64/pci/pci.c b/arch/arm64/pci/pci.c index b03b0eb..35d99b3 100644 --- a/arch/arm64/pci/pci.c +++ b/arch/arm64/pci/pci.c @@ -3,22 +3,30 @@ #include <linux/kernel.h> #include <linux/pci.h>
+const struct pci_raw_ops *__read_mostly raw_pci_ops; + /** * raw_pci_read - Platform-specific PCI config space access. * * Default empty implementation. Replace with an architecture-specific setup * routine, if necessary. */ -int __weak raw_pci_read(unsigned int domain, unsigned int bus, - unsigned int devfn, int reg, int len, u32 *val) +int raw_pci_read(unsigned int domain, unsigned int bus, + unsigned int devfn, int reg, int len, u32 *val) { - return -EINVAL; + if (!raw_pci_ops) + return -EINVAL; + + return raw_pci_ops->read(domain, bus, devfn, reg, len, val); }
-int __weak raw_pci_write(unsigned int domain, unsigned int bus, - unsigned int devfn, int reg, int len, u32 val) +int raw_pci_write(unsigned int domain, unsigned int bus, + unsigned int devfn, int reg, int len, u32 val) { - return -EINVAL; + if (!raw_pci_ops) + return -EINVAL; + + return raw_pci_ops->write(domain, bus, devfn, reg, len, val); }
/* Root bridge scanning */
On Monday 10 November 2014 19:47:10 al.stone@linaro.org wrote:
From: Mark Salter msalter@redhat.com
The current code provides placeholder weak functions for for raw_pci_read() and raw_pci_write(). This patch creates a global 'raw_pci_ops' variable which points to a struct containing read and write ops. The weak attribute is removed from raw_pci_read() and raw_pci_write() which will use the ops provided by the raw_pci_ops pointer.
Signed-off-by: Mark Salter msalter@redhat.com
This should not be required at all, just hardcode mmconfig for ACPI as the spec says.
Arnd
From: Mark Salter msalter@redhat.com
Add support for parsing MCFG table and provide functions to read/write PCI configuration space based on the parsed info. This provides the low-level raw_pci_read/raw_pci_write functionality.
Signed-off-by: Mark Salter msalter@redhat.com --- arch/arm64/Kconfig | 3 + arch/arm64/include/asm/pci.h | 16 ++ arch/arm64/pci/Makefile | 1 + arch/arm64/pci/mmconfig.c | 409 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 429 insertions(+) create mode 100644 arch/arm64/pci/mmconfig.c
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 7789db7..fd2fa10 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -186,6 +186,9 @@ config PCI_DOMAINS_GENERIC config PCI_SYSCALL def_bool PCI
+config PCI_MMCONFIG + def_bool y if PCI && ACPI + source "drivers/pci/Kconfig" source "drivers/pci/pcie/Kconfig" source "drivers/pci/hotplug/Kconfig" diff --git a/arch/arm64/include/asm/pci.h b/arch/arm64/include/asm/pci.h index 9e23df3..3b04035 100644 --- a/arch/arm64/include/asm/pci.h +++ b/arch/arm64/include/asm/pci.h @@ -48,5 +48,21 @@ struct pci_raw_ops {
extern const struct pci_raw_ops *raw_pci_ops;
+/* "PCI MMCONFIG %04x [bus %02x-%02x]" */ +#define PCI_MMCFG_RESOURCE_NAME_LEN (22 + 4 + 2 + 2) + +struct pci_mmcfg_region { + struct list_head list; + struct resource res; + u64 address; + char __iomem *virt; + u16 segment; + u8 start_bus; + u8 end_bus; + char name[PCI_MMCFG_RESOURCE_NAME_LEN]; +}; + +extern struct pci_mmcfg_region *pci_mmconfig_lookup(int segment, int bus); + #endif /* __KERNEL__ */ #endif /* __ASM_PCI_H */ diff --git a/arch/arm64/pci/Makefile b/arch/arm64/pci/Makefile index b8d5dbd..7038b51 100644 --- a/arch/arm64/pci/Makefile +++ b/arch/arm64/pci/Makefile @@ -1 +1,2 @@ obj-y += pci.o +obj-$(CONFIG_ACPI) += mmconfig.o diff --git a/arch/arm64/pci/mmconfig.c b/arch/arm64/pci/mmconfig.c new file mode 100644 index 0000000..0bda9b7 --- /dev/null +++ b/arch/arm64/pci/mmconfig.c @@ -0,0 +1,409 @@ +/* + * mmconfig.c - Low-level direct PCI config space access via MMCONFIG + * + * Borrowed heavily from x86 + */ + +#include <linux/pci.h> +#include <linux/acpi.h> +#include <linux/init.h> +#include <linux/bitmap.h> +#include <linux/dmi.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/rculist.h> +#include <linux/rcupdate.h> + +#define PREFIX "PCI: " + +/* Indicate if the mmcfg resources have been placed into the resource table. */ +static bool pci_mmcfg_running_state; +static bool pci_mmcfg_arch_init_failed; +static DEFINE_MUTEX(pci_mmcfg_lock); + +#define PCI_MMCFG_BUS_OFFSET(bus) ((bus) << 20) + +LIST_HEAD(pci_mmcfg_list); + +static inline unsigned char mmio_config_readb(void __iomem *pos) +{ + return readb(pos); +} + +static inline unsigned short mmio_config_readw(void __iomem *pos) +{ + return readw(pos); +} + +static inline unsigned int mmio_config_readl(void __iomem *pos) +{ + return readl(pos); +} + +static inline void mmio_config_writeb(void __iomem *pos, u8 val) +{ + writeb(val, pos); +} + +static inline void mmio_config_writew(void __iomem *pos, u16 val) +{ + writew(val, pos); +} + +static inline void mmio_config_writel(void __iomem *pos, u32 val) +{ + writel(val, pos); +} + +struct pci_mmcfg_region *pci_mmconfig_lookup(int segment, int bus) +{ + struct pci_mmcfg_region *cfg; + + list_for_each_entry_rcu(cfg, &pci_mmcfg_list, list) + if (cfg->segment == segment && + cfg->start_bus <= bus && bus <= cfg->end_bus) + return cfg; + + return NULL; +} + +static char __iomem *pci_dev_base(struct pci_mmcfg_region *cfg, + unsigned int bus, unsigned int devfn) +{ + return cfg->virt + (PCI_MMCFG_BUS_OFFSET(bus) | (devfn << 12)); +} + +static int __pci_mmcfg_read(struct pci_mmcfg_region *cfg, unsigned int bus, + unsigned int devfn, int reg, int len, u32 *value) +{ + char __iomem *addr = pci_dev_base(cfg, bus, devfn); + + switch (len) { + case 1: + *value = mmio_config_readb(addr + reg); + break; + case 2: + *value = mmio_config_readw(addr + reg); + break; + case 4: + *value = mmio_config_readl(addr + reg); + break; + } + return 0; +} + +static int pci_mmcfg_read(unsigned int seg, unsigned int bus, + unsigned int devfn, int reg, int len, u32 *value) +{ + struct pci_mmcfg_region *cfg; + int ret; + + /* Why do we have this when nobody checks it. How about a BUG()!? -AK */ + if (unlikely((bus > 255) || (devfn > 255) || (reg > 4095))) { +err: *value = -1; + return -EINVAL; + } + + rcu_read_lock(); + cfg = pci_mmconfig_lookup(seg, bus); + if (!cfg || !cfg->virt) { + rcu_read_unlock(); + goto err; + } + + ret = __pci_mmcfg_read(cfg, bus, devfn, reg, len, value); + + rcu_read_unlock(); + + return ret; +} + +static int __pci_mmcfg_write(struct pci_mmcfg_region *cfg, unsigned int bus, + unsigned int devfn, int reg, int len, u32 value) +{ + char __iomem *addr = pci_dev_base(cfg, bus, devfn); + + switch (len) { + case 1: + mmio_config_writeb(addr + reg, value); + break; + case 2: + mmio_config_writew(addr + reg, value); + break; + case 4: + mmio_config_writel(addr + reg, value); + break; + } + return 0; +} + +static int pci_mmcfg_write(unsigned int seg, unsigned int bus, + unsigned int devfn, int reg, int len, u32 value) +{ + struct pci_mmcfg_region *cfg; + int ret; + + /* Why do we have this when nobody checks it. How about a BUG()!? -AK */ + if (unlikely((bus > 255) || (devfn > 255) || (reg > 4095))) + return -EINVAL; + + rcu_read_lock(); + cfg = pci_mmconfig_lookup(seg, bus); + if (!cfg || !cfg->virt) { + rcu_read_unlock(); + return -EINVAL; + } + + ret = __pci_mmcfg_write(cfg, bus, devfn, reg, len, value); + + rcu_read_unlock(); + + return ret; +} + +const struct pci_raw_ops pci_mmcfg = { + .read = pci_mmcfg_read, + .write = pci_mmcfg_write, +}; + +static void __iomem *mcfg_ioremap(struct pci_mmcfg_region *cfg) +{ + void __iomem *addr; + u64 start, size; + int num_buses; + + start = cfg->address + PCI_MMCFG_BUS_OFFSET(cfg->start_bus); + num_buses = cfg->end_bus - cfg->start_bus + 1; + size = PCI_MMCFG_BUS_OFFSET(num_buses); + addr = ioremap_nocache(start, size); + if (addr) + addr -= PCI_MMCFG_BUS_OFFSET(cfg->start_bus); + return addr; +} + +void pci_mmcfg_arch_unmap(struct pci_mmcfg_region *cfg) +{ + if (cfg && cfg->virt) { + iounmap(cfg->virt + PCI_MMCFG_BUS_OFFSET(cfg->start_bus)); + cfg->virt = NULL; + } +} + +void __init pci_mmcfg_arch_free(void) +{ + struct pci_mmcfg_region *cfg; + + list_for_each_entry(cfg, &pci_mmcfg_list, list) + pci_mmcfg_arch_unmap(cfg); +} + +int pci_mmcfg_arch_map(struct pci_mmcfg_region *cfg) +{ + cfg->virt = mcfg_ioremap(cfg); + if (!cfg->virt) { + pr_err(PREFIX "can't map MMCONFIG at %pR\n", &cfg->res); + return -ENOMEM; + } + + return 0; +} + +static void __init pci_mmconfig_remove(struct pci_mmcfg_region *cfg) +{ + if (cfg->res.parent) + release_resource(&cfg->res); + list_del(&cfg->list); + kfree(cfg); +} + +static void __init free_all_mmcfg(void) +{ + struct pci_mmcfg_region *cfg, *tmp; + + pci_mmcfg_arch_free(); + list_for_each_entry_safe(cfg, tmp, &pci_mmcfg_list, list) + pci_mmconfig_remove(cfg); +} + +static void list_add_sorted(struct pci_mmcfg_region *new) +{ + struct pci_mmcfg_region *cfg; + + /* keep list sorted by segment and starting bus number */ + list_for_each_entry_rcu(cfg, &pci_mmcfg_list, list) { + if (cfg->segment > new->segment || + (cfg->segment == new->segment && + cfg->start_bus >= new->start_bus)) { + list_add_tail_rcu(&new->list, &cfg->list); + return; + } + } + list_add_tail_rcu(&new->list, &pci_mmcfg_list); +} + +static struct pci_mmcfg_region *pci_mmconfig_alloc(int segment, int start, + int end, u64 addr) +{ + struct pci_mmcfg_region *new; + struct resource *res; + + if (addr == 0) + return NULL; + + new = kzalloc(sizeof(*new), GFP_KERNEL); + if (!new) + return NULL; + + new->address = addr; + new->segment = segment; + new->start_bus = start; + new->end_bus = end; + + res = &new->res; + res->start = addr + PCI_MMCFG_BUS_OFFSET(start); + res->end = addr + PCI_MMCFG_BUS_OFFSET(end + 1) - 1; + res->flags = IORESOURCE_MEM | IORESOURCE_BUSY; + snprintf(new->name, PCI_MMCFG_RESOURCE_NAME_LEN, + "PCI MMCONFIG %04x [bus %02x-%02x]", segment, start, end); + res->name = new->name; + + return new; +} + +static struct pci_mmcfg_region *__init pci_mmconfig_add(int segment, int start, + int end, u64 addr) +{ + struct pci_mmcfg_region *new; + + new = pci_mmconfig_alloc(segment, start, end, addr); + if (new) { + mutex_lock(&pci_mmcfg_lock); + list_add_sorted(new); + mutex_unlock(&pci_mmcfg_lock); + + pr_info(PREFIX + "MMCONFIG for domain %04x [bus %02x-%02x] at %pR " + "(base %#lx)\n", + segment, start, end, &new->res, (unsigned long)addr); + } + + return new; +} + +static int __init pci_parse_mcfg(struct acpi_table_header *header) +{ + struct acpi_table_mcfg *mcfg; + struct acpi_mcfg_allocation *cfg_table, *cfg; + unsigned long i; + int entries; + + if (!header) + return -EINVAL; + + mcfg = (struct acpi_table_mcfg *)header; + + /* how many config structures do we have */ + free_all_mmcfg(); + entries = 0; + i = header->length - sizeof(struct acpi_table_mcfg); + while (i >= sizeof(struct acpi_mcfg_allocation)) { + entries++; + i -= sizeof(struct acpi_mcfg_allocation); + } + if (entries == 0) { + pr_err(PREFIX "MMCONFIG has no entries\n"); + return -ENODEV; + } + + cfg_table = (struct acpi_mcfg_allocation *) &mcfg[1]; + for (i = 0; i < entries; i++) { + cfg = &cfg_table[i]; + + if (pci_mmconfig_add(cfg->pci_segment, cfg->start_bus_number, + cfg->end_bus_number, cfg->address) == NULL) { + pr_warn(PREFIX "no memory for MCFG entries\n"); + free_all_mmcfg(); + return -ENOMEM; + } + } + + return 0; +} + +int __init pci_mmcfg_arch_init(void) +{ + struct pci_mmcfg_region *cfg; + + list_for_each_entry(cfg, &pci_mmcfg_list, list) + if (pci_mmcfg_arch_map(cfg)) { + pci_mmcfg_arch_free(); + return 0; + } + + raw_pci_ops = &pci_mmcfg; + + return 1; +} + +static void __init __pci_mmcfg_init(int early) +{ + if (list_empty(&pci_mmcfg_list)) { + pr_info("No MCFG table found!\n"); + pci_mmcfg_arch_init_failed = true; + return; + } + + if (!pci_mmcfg_arch_init()) { + pr_info("pci_mmcfg_arch_init failed!\n"); + free_all_mmcfg(); + pci_mmcfg_arch_init_failed = true; + } +} + +void __init pci_mmcfg_early_init(void) +{ + acpi_table_parse(ACPI_SIG_MCFG, pci_parse_mcfg); + + __pci_mmcfg_init(1); +} + +static int __init pci_mmcfg_init(void) +{ + pci_mmcfg_early_init(); + return 0; +} +arch_initcall(pci_mmcfg_init); + +void __init pci_mmcfg_late_init(void) +{ + /* MMCONFIG hasn't been enabled yet, try again */ + if (pci_mmcfg_arch_init_failed) { + acpi_table_parse(ACPI_SIG_MCFG, pci_parse_mcfg); + __pci_mmcfg_init(0); + } +} + +static int __init pci_mmcfg_late_insert_resources(void) +{ + struct pci_mmcfg_region *cfg; + + pci_mmcfg_running_state = true; + + /* + * Attempt to insert the mmcfg resources but not with the busy flag + * marked so it won't cause request errors when __request_region is + * called. + */ + list_for_each_entry(cfg, &pci_mmcfg_list, list) + if (!cfg->res.parent) + insert_resource(&iomem_resource, &cfg->res); + + return 0; +} + +/* + * Perform MMCONFIG resource insertion after PCI initialization to allow for + * misprogrammed MCFG tables that state larger sizes but actually conflict + * with other system resources. + */ +late_initcall(pci_mmcfg_late_insert_resources);
From: Mark Salter msalter@redhat.com
Some MCFG tables may be broken or the underlying hardware may not be fully compliant with the PCIe ECAM mechanism. This patch provides a mechanism to override the default mmconfig read/write routines and/or do other MCFG related fixups.
Signed-off-by: Mark Salter msalter@redhat.com --- arch/arm64/include/asm/pci.h | 24 ++++++++++++++++++++++++ arch/arm64/pci/mmconfig.c | 30 ++++++++++++++++++++++++++---- include/asm-generic/vmlinux.lds.h | 7 +++++++ 3 files changed, 57 insertions(+), 4 deletions(-)
diff --git a/arch/arm64/include/asm/pci.h b/arch/arm64/include/asm/pci.h index 3b04035..b850d6e 100644 --- a/arch/arm64/include/asm/pci.h +++ b/arch/arm64/include/asm/pci.h @@ -51,9 +51,21 @@ extern const struct pci_raw_ops *raw_pci_ops; /* "PCI MMCONFIG %04x [bus %02x-%02x]" */ #define PCI_MMCFG_RESOURCE_NAME_LEN (22 + 4 + 2 + 2)
+struct acpi_pci_root; +struct pci_mmcfg_region; + +typedef int (*acpi_mcfg_fixup_t)(struct acpi_pci_root *root, + struct pci_mmcfg_region *cfg); + struct pci_mmcfg_region { struct list_head list; struct resource res; + int (*read)(struct pci_mmcfg_region *cfg, unsigned int bus, + unsigned int devfn, int reg, int len, u32 *value); + int (*write)(struct pci_mmcfg_region *cfg, unsigned int bus, + unsigned int devfn, int reg, int len, u32 value); + acpi_mcfg_fixup_t fixup; + void *data; u64 address; char __iomem *virt; u16 segment; @@ -62,6 +74,18 @@ struct pci_mmcfg_region { char name[PCI_MMCFG_RESOURCE_NAME_LEN]; };
+struct acpi_mcfg_fixup { + char oem_id[7]; + char oem_table_id[9]; + acpi_mcfg_fixup_t hook; +}; + +/* Designate a routine to fix up buggy MCFG */ +#define DECLARE_ACPI_MCFG_FIXUP(oem_id, table_id, hook) \ + static const struct acpi_mcfg_fixup __acpi_fixup_##hook __used \ + __attribute__((__section__(".acpi_fixup_mcfg"), aligned((sizeof(void *))))) \ + = { {oem_id}, {table_id}, hook }; + extern struct pci_mmcfg_region *pci_mmconfig_lookup(int segment, int bus);
#endif /* __KERNEL__ */ diff --git a/arch/arm64/pci/mmconfig.c b/arch/arm64/pci/mmconfig.c index 0bda9b7..110fec7 100644 --- a/arch/arm64/pci/mmconfig.c +++ b/arch/arm64/pci/mmconfig.c @@ -111,7 +111,10 @@ err: *value = -1; goto err; }
- ret = __pci_mmcfg_read(cfg, bus, devfn, reg, len, value); + if (cfg->read) + ret = (*cfg->read)(cfg, bus, devfn, reg, len, value); + else + ret = __pci_mmcfg_read(cfg, bus, devfn, reg, len, value);
rcu_read_unlock();
@@ -154,7 +157,10 @@ static int pci_mmcfg_write(unsigned int seg, unsigned int bus, return -EINVAL; }
- ret = __pci_mmcfg_write(cfg, bus, devfn, reg, len, value); + if (cfg->write) + ret = (*cfg->write)(cfg, bus, devfn, reg, len, value); + else + ret = __pci_mmcfg_write(cfg, bus, devfn, reg, len, value);
rcu_read_unlock();
@@ -290,10 +296,15 @@ static struct pci_mmcfg_region *__init pci_mmconfig_add(int segment, int start, return new; }
+extern struct acpi_mcfg_fixup __start_acpi_mcfg_fixups[]; +extern struct acpi_mcfg_fixup __end_acpi_mcfg_fixups[]; + static int __init pci_parse_mcfg(struct acpi_table_header *header) { struct acpi_table_mcfg *mcfg; struct acpi_mcfg_allocation *cfg_table, *cfg; + struct acpi_mcfg_fixup *fixup; + struct pci_mmcfg_region *new; unsigned long i; int entries;
@@ -315,16 +326,27 @@ static int __init pci_parse_mcfg(struct acpi_table_header *header) return -ENODEV; }
+ fixup = __start_acpi_mcfg_fixups; + while (fixup < __end_acpi_mcfg_fixups) { + if (!strncmp(fixup->oem_id, header->oem_id, 6) && + !strncmp(fixup->oem_table_id, header->oem_table_id, 8)) + break; + ++fixup; + } + cfg_table = (struct acpi_mcfg_allocation *) &mcfg[1]; for (i = 0; i < entries; i++) { cfg = &cfg_table[i];
- if (pci_mmconfig_add(cfg->pci_segment, cfg->start_bus_number, - cfg->end_bus_number, cfg->address) == NULL) { + new = pci_mmconfig_add(cfg->pci_segment, cfg->start_bus_number, + cfg->end_bus_number, cfg->address); + if (!new) { pr_warn(PREFIX "no memory for MCFG entries\n"); free_all_mmcfg(); return -ENOMEM; } + if (fixup < __end_acpi_mcfg_fixups) + new->fixup = fixup->hook; }
return 0; diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h index aa70cbd..1261fef 100644 --- a/include/asm-generic/vmlinux.lds.h +++ b/include/asm-generic/vmlinux.lds.h @@ -275,6 +275,13 @@ VMLINUX_SYMBOL(__end_pci_fixups_suspend_late) = .; \ } \ \ + /* ACPI quirks */ \ + .acpi_fixup : AT(ADDR(.acpi_fixup) - LOAD_OFFSET) { \ + VMLINUX_SYMBOL(__start_acpi_mcfg_fixups) = .; \ + *(.acpi_fixup_mcfg) \ + VMLINUX_SYMBOL(__end_acpi_mcfg_fixups) = .; \ + } \ + \ /* Built-in firmware blobs */ \ .builtin_fw : AT(ADDR(.builtin_fw) - LOAD_OFFSET) { \ VMLINUX_SYMBOL(__start_builtin_fw) = .; \
From: Mark Salter msalter@redhat.com
Xgene doesn't decode bus bits of mmconfig region and only supports devfn 0 of bus 0. For other buses/devices, some internal registers need to be poked. This patch provides a fixup to support ACPI MCFG tables.
Signed-off-by: Mark Salter msalter@redhat.com --- drivers/pci/host/pci-xgene.c | 144 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+)
diff --git a/drivers/pci/host/pci-xgene.c b/drivers/pci/host/pci-xgene.c index 9ecabfa..526812f 100644 --- a/drivers/pci/host/pci-xgene.c +++ b/drivers/pci/host/pci-xgene.c @@ -29,6 +29,7 @@ #include <linux/pci.h> #include <linux/platform_device.h> #include <linux/slab.h> +#include <linux/acpi.h>
#define PCIECORE_CTLANDSTATUS 0x50 #define PIM1_1L 0x80 @@ -600,6 +601,149 @@ static int xgene_pcie_setup(struct xgene_pcie_port *port, return 0; }
+#ifdef CONFIG_ACPI +struct xgene_mcfg_info { + void __iomem *csr_base; +}; + +/* + * When the address bit [17:16] is 2'b01, the Configuration access will be + * treated as Type 1 and it will be forwarded to external PCIe device. + */ +static void __iomem *__get_cfg_base(struct pci_mmcfg_region *cfg, + unsigned int bus) +{ + if (bus > cfg->start_bus) + return cfg->virt + AXI_EP_CFG_ACCESS; + + return cfg->virt; +} + +/* + * For Configuration request, RTDID register is used as Bus Number, + * Device Number and Function number of the header fields. + */ +static void __set_rtdid_reg(struct pci_mmcfg_region *cfg, + unsigned int bus, unsigned int devfn) +{ + struct xgene_mcfg_info *info = cfg->data; + unsigned int b, d, f; + u32 rtdid_val = 0; + + b = bus; + d = PCI_SLOT(devfn); + f = PCI_FUNC(devfn); + + if (bus != cfg->start_bus) + rtdid_val = (b << 8) | (d << 3) | f; + + writel(rtdid_val, info->csr_base + RTDID); + /* read the register back to ensure flush */ + readl(info->csr_base + RTDID); +} + +static int xgene_raw_pci_read(struct pci_mmcfg_region *cfg, unsigned int bus, + unsigned int devfn, int offset, int len, u32 *val) +{ + void __iomem *addr; + + if (bus == cfg->start_bus && devfn != 0) { + *val = 0xffffffff; + return PCIBIOS_DEVICE_NOT_FOUND; + } + + __set_rtdid_reg(cfg, bus, devfn); + addr = __get_cfg_base(cfg, bus); + switch (len) { + case 1: + xgene_pcie_cfg_in8(addr, offset, val); + break; + case 2: + xgene_pcie_cfg_in16(addr, offset, val); + break; + default: + xgene_pcie_cfg_in32(addr, offset, val); + break; + } + return PCIBIOS_SUCCESSFUL; +} + +static int xgene_raw_pci_write(struct pci_mmcfg_region *cfg, unsigned int bus, + unsigned int devfn, int offset, int len, u32 val) +{ + void __iomem *addr; + + if (bus == cfg->start_bus && devfn != 0) + return PCIBIOS_DEVICE_NOT_FOUND; + + __set_rtdid_reg(cfg, bus, devfn); + addr = __get_cfg_base(cfg, bus); + switch (len) { + case 1: + xgene_pcie_cfg_out8(addr, offset, (u8)val); + break; + case 2: + xgene_pcie_cfg_out16(addr, offset, (u16)val); + break; + default: + xgene_pcie_cfg_out32(addr, offset, val); + break; + } + return PCIBIOS_SUCCESSFUL; +} + +static acpi_status find_csr_base(struct acpi_resource *acpi_res, void *data) +{ + struct pci_mmcfg_region *cfg = data; + struct xgene_mcfg_info *info = cfg->data; + struct acpi_resource_fixed_memory32 *fixed32; + + if (acpi_res->type == ACPI_RESOURCE_TYPE_FIXED_MEMORY32) { + fixed32 = &acpi_res->data.fixed_memory32; + info->csr_base = ioremap(fixed32->address, + fixed32->address_length); + return AE_CTRL_TERMINATE; + } + return AE_OK; +} + +static int xgene_mcfg_fixup(struct acpi_pci_root *root, + struct pci_mmcfg_region *cfg) +{ + struct acpi_device *device = root->device; + struct xgene_mcfg_info *info; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (info == NULL) + return -ENOMEM; + + cfg->data = info; + + acpi_walk_resources(device->handle, METHOD_NAME__CRS, + find_csr_base, cfg); + + if (!info->csr_base) { + kfree(info); + cfg->data = NULL; + return -ENODEV; + } + + cfg->read = xgene_raw_pci_read; + cfg->write = xgene_raw_pci_write; + + /* actual last bus reachable through this mmconfig */ + cfg->end_bus = root->secondary.end; + + /* firmware should have done this */ + xgene_raw_pci_write(cfg, cfg->start_bus, 0, PCI_PRIMARY_BUS, 4, + cfg->start_bus | ((cfg->start_bus + 1) << 8) | + (cfg->end_bus << 16)); + + return 0; +} +DECLARE_ACPI_MCFG_FIXUP("APM ", "XGENE ", xgene_mcfg_fixup); +#endif /* CONFIG_ACPI */ + static int xgene_pcie_probe_bridge(struct platform_device *pdev) { struct device_node *dn = pdev->dev.of_node;
On Monday 10 November 2014 19:47:13 al.stone@linaro.org wrote:
From: Mark Salter msalter@redhat.com
Xgene doesn't decode bus bits of mmconfig region and only supports devfn 0 of bus 0. For other buses/devices, some internal registers need to be poked. This patch provides a fixup to support ACPI MCFG tables.
Signed-off-by: Mark Salter msalter@redhat.com
The pci-xgene driver doesn't seem suitable for ACPI, I really don't want to see hacks like this upstream. Both ACPI and SBSA require ECAM compliant mmconfig, but this driver doesn't use mmconfig at all, you hack it up to use a private configuration space access.
Arnd
On Tue, 2014-11-11 at 14:51 +0100, Arnd Bergmann wrote:
On Monday 10 November 2014 19:47:13 al.stone@linaro.org wrote:
From: Mark Salter msalter@redhat.com
Xgene doesn't decode bus bits of mmconfig region and only supports devfn 0 of bus 0. For other buses/devices, some internal registers need to be poked. This patch provides a fixup to support ACPI MCFG tables.
Signed-off-by: Mark Salter msalter@redhat.com
The pci-xgene driver doesn't seem suitable for ACPI, I really don't want to see hacks like this upstream. Both ACPI and SBSA require ECAM compliant mmconfig, but this driver doesn't use mmconfig at all, you hack it up to use a private configuration space access.
I have no intention of pushing that upstream. I have reorganized the fedorahosted tree to make that clearer.
On Tuesday 11 November 2014 09:17:51 Mark Salter wrote:
On Tue, 2014-11-11 at 14:51 +0100, Arnd Bergmann wrote:
On Monday 10 November 2014 19:47:13 al.stone@linaro.org wrote:
From: Mark Salter msalter@redhat.com
Xgene doesn't decode bus bits of mmconfig region and only supports devfn 0 of bus 0. For other buses/devices, some internal registers need to be poked. This patch provides a fixup to support ACPI MCFG tables.
Signed-off-by: Mark Salter msalter@redhat.com
The pci-xgene driver doesn't seem suitable for ACPI, I really don't want to see hacks like this upstream. Both ACPI and SBSA require ECAM compliant mmconfig, but this driver doesn't use mmconfig at all, you hack it up to use a private configuration space access.
I have no intention of pushing that upstream. I have reorganized the fedorahosted tree to make that clearer.
Ok, thanks a lot!
Arnd
From: Mark Salter msalter@redhat.com
Fix a NULL dereference in find_mmu_master which occurs when booting with ACPI. In that case, PCI bridges with not have an of_node. Add a check for NULL of_node and bail out if that is the case.
Signed-off-by: Mark Salter msalter@redhat.com --- drivers/iommu/arm-smmu.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c index 60558f7..dae8ae3 100644 --- a/drivers/iommu/arm-smmu.c +++ b/drivers/iommu/arm-smmu.c @@ -444,7 +444,10 @@ static struct device_node *dev_get_dev_node(struct device *dev)
while (!pci_is_root_bus(bus)) bus = bus->parent; - return bus->bridge->parent->of_node; + if (bus->bridge->parent) + return bus->bridge->parent->of_node; + else + return bus->bridge->of_node; }
return dev->of_node; @@ -560,6 +563,9 @@ static struct arm_smmu_device *find_smmu_for_device(struct device *dev) struct arm_smmu_master *master = NULL; struct device_node *dev_node = dev_get_dev_node(dev);
+ if (!dev_node) + return NULL; + spin_lock(&arm_smmu_devices_lock); list_for_each_entry(smmu, &arm_smmu_devices, list) { master = find_smmu_master(smmu, dev_node);
From: Al Stone ahs3@redhat.com
This is a first pass of ACPI probing of PCI; much of it comes from existing ia64 and x86 code, and will need refactoring later.
Signed-off-by: Mark Salter msalter@redhat.com --- arch/arm64/include/asm/pci.h | 8 + arch/arm64/kernel/pci.c | 108 +++++++++----- arch/arm64/pci/pci.c | 341 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 421 insertions(+), 36 deletions(-)
diff --git a/arch/arm64/include/asm/pci.h b/arch/arm64/include/asm/pci.h index b850d6e..faad64c 100644 --- a/arch/arm64/include/asm/pci.h +++ b/arch/arm64/include/asm/pci.h @@ -53,6 +53,14 @@ extern const struct pci_raw_ops *raw_pci_ops;
struct acpi_pci_root; struct pci_mmcfg_region; +struct acpi_device; + +struct pci_sysdata { + int domain; /* PCI domain */ + int node; /* NUMA node */ + struct acpi_device *companion; /* ACPI companion device */ + void *iommu; /* IOMMU private data */ +};
typedef int (*acpi_mcfg_fixup_t)(struct acpi_pci_root *root, struct pci_mmcfg_region *cfg); diff --git a/arch/arm64/kernel/pci.c b/arch/arm64/kernel/pci.c index 42fb195..47f1db1 100644 --- a/arch/arm64/kernel/pci.c +++ b/arch/arm64/kernel/pci.c @@ -18,6 +18,8 @@ #include <linux/of_pci.h> #include <linux/of_platform.h> #include <linux/slab.h> +#include <linux/acpi.h> +#include <linux/pci-acpi.h>
#include <asm/pci-bridge.h>
@@ -38,61 +40,97 @@ resource_size_t pcibios_align_resource(void *data, const struct resource *res, return res->start; }
+int pcibios_root_bridge_prepare(struct pci_host_bridge *bridge) +{ + struct pci_sysdata *sd; + + if (!acpi_disabled) { + sd = bridge->bus->sysdata; + ACPI_COMPANION_SET(&bridge->dev, sd->companion); + } + return 0; +} + /* * Try to assign the IRQ number from DT when adding a new device */ int pcibios_add_device(struct pci_dev *dev) { - dev->irq = of_irq_parse_and_map_pci(dev, 0, 0); + if (acpi_disabled) + dev->irq = of_irq_parse_and_map_pci(dev, 0, 0);
return 0; }
+void pcibios_add_bus(struct pci_bus *bus) +{ + if (!acpi_disabled) + acpi_pci_add_bus(bus); +}
-#ifdef CONFIG_PCI_DOMAINS_GENERIC -static bool dt_domain_found = false; - -void pci_bus_assign_domain_nr(struct pci_bus *bus, struct device *parent) +void pcibios_remove_bus(struct pci_bus *bus) { - int domain = of_get_pci_domain_nr(parent->of_node); - - if (domain >= 0) { - dt_domain_found = true; - } else if (dt_domain_found == true) { - dev_err(parent, "Node %s is missing "linux,pci-domain" property in DT\n", - parent->of_node->full_name); - return; - } else { - domain = pci_get_new_domain_nr(); - } + if (!acpi_disabled) + acpi_pci_remove_bus(bus); +}
- bus->domain_nr = domain; +int pcibios_enable_irq(struct pci_dev *dev) +{ + if (!acpi_disabled && !pci_dev_msi_enabled(dev)) + acpi_pci_irq_enable(dev); + return 0; } -#endif
-/* - * raw_pci_read/write - Platform-specific PCI config space access. - * - * Default empty implementation. Replace with an architecture-specific setup - * routine, if necessary. - */ -int raw_pci_read(unsigned int domain, unsigned int bus, - unsigned int devfn, int reg, int len, u32 *val) +int pcibios_disable_irq(struct pci_dev *dev) { - return -EINVAL; + if (!acpi_disabled && !pci_dev_msi_enabled(dev)) + acpi_pci_irq_disable(dev); + return 0; }
-int raw_pci_write(unsigned int domain, unsigned int bus, - unsigned int devfn, int reg, int len, u32 val) +int pcibios_enable_device(struct pci_dev *dev, int bars) { - return -EINVAL; + int err; + + if ((err = pci_enable_resources(dev, bars)) < 0) + return err; + + if (!pci_dev_msi_enabled(dev)) + return pcibios_enable_irq(dev); + return 0; }
-#ifdef CONFIG_ACPI -/* Root bridge scanning */ -struct pci_bus *pci_acpi_scan_root(struct acpi_pci_root *root) +#ifdef CONFIG_PCI_DOMAINS_GENERIC +void pci_bus_assign_domain_nr(struct pci_bus *bus, struct device *parent) { - /* TODO: Should be revisited when implementing PCI on ACPI */ - return NULL; + int domain = -1; + + if (acpi_disabled) + domain = of_get_pci_domain_nr(parent->of_node); + else { + struct pci_sysdata *sd = bus->sysdata; + domain = sd->domain; + } + if (domain >= 0) + bus->domain_nr = domain; } #endif + +static int __init pcibios_assign_resources(void) +{ + struct pci_bus *root_bus; + + if (acpi_disabled) + return 0; + + list_for_each_entry(root_bus, &pci_root_buses, node) { + pcibios_resource_survey_bus(root_bus); + pci_assign_unassigned_root_bus_resources(root_bus); + } + return 0; +} +/* + * fs_initcall comes after subsys_initcall, so we know acpi scan + * has run. + */ +fs_initcall(pcibios_assign_resources); diff --git a/arch/arm64/pci/pci.c b/arch/arm64/pci/pci.c index 35d99b3..a1f8b08 100644 --- a/arch/arm64/pci/pci.c +++ b/arch/arm64/pci/pci.c @@ -1,8 +1,21 @@ #include <linux/acpi.h> +#include <linux/of_address.h> #include <linux/types.h> #include <linux/kernel.h> #include <linux/pci.h>
+struct pci_root_info { + struct acpi_device *bridge; + char name[16]; + unsigned int res_num; + struct resource *res; + resource_size_t *res_offset; + struct pci_sysdata sd; + u16 segment; + u8 start_bus; + u8 end_bus; +}; + const struct pci_raw_ops *__read_mostly raw_pci_ops;
/** @@ -29,8 +42,334 @@ int raw_pci_write(unsigned int domain, unsigned int bus, return raw_pci_ops->write(domain, bus, devfn, reg, len, val); }
+static int pci_read(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *value) +{ + return raw_pci_read(pci_domain_nr(bus), bus->number, + devfn, where, size, value); +} + +static int pci_write(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 value) +{ + return raw_pci_write(pci_domain_nr(bus), bus->number, + devfn, where, size, value); +} + +static struct pci_ops pci_root_ops = { + .read = pci_read, + .write = pci_write, +}; + +static acpi_status resource_to_addr(struct acpi_resource *resource, + struct acpi_resource_address64 *addr) +{ + acpi_status status; + + memset(addr, 0, sizeof(*addr)); + switch (resource->type) { + case ACPI_RESOURCE_TYPE_ADDRESS16: + case ACPI_RESOURCE_TYPE_ADDRESS32: + case ACPI_RESOURCE_TYPE_ADDRESS64: + status = acpi_resource_to_address64(resource, addr); + if (ACPI_SUCCESS(status) && + (addr->resource_type == ACPI_MEMORY_RANGE || + addr->resource_type == ACPI_IO_RANGE) && + addr->address_length > 0) { + return AE_OK; + } + break; + } + return AE_ERROR; +} + +static acpi_status count_resource(struct acpi_resource *acpi_res, void *data) +{ + struct pci_root_info *info = data; + struct acpi_resource_address64 addr; + acpi_status status; + + status = resource_to_addr(acpi_res, &addr); + if (ACPI_SUCCESS(status)) + info->res_num++; + return AE_OK; +} + +static acpi_status setup_resource(struct acpi_resource *acpi_res, void *data) +{ + struct pci_root_info *info = data; + struct resource *res; + struct acpi_resource_address64 addr; + acpi_status status; + unsigned long flags; + u64 start, end; + + status = resource_to_addr(acpi_res, &addr); + if (!ACPI_SUCCESS(status)) + return AE_OK; + + if (addr.resource_type == ACPI_MEMORY_RANGE) { + flags = IORESOURCE_MEM; + if (addr.info.mem.caching == ACPI_PREFETCHABLE_MEMORY) + flags |= IORESOURCE_PREFETCH; + } else if (addr.resource_type == ACPI_IO_RANGE) { + flags = IORESOURCE_IO; + } else + return AE_OK; + + start = addr.minimum + addr.translation_offset; + end = addr.maximum + addr.translation_offset; + + res = &info->res[info->res_num]; + res->name = info->name; + res->flags = flags; + res->start = start; + res->end = end; + + if (flags & IORESOURCE_IO) { + unsigned long port; + int err; + + err = pci_register_io_range(start, addr.address_length); + if (err) + return AE_OK; + + port = pci_address_to_pio(start); + if (port == (unsigned long)-1) { + res->start = -1; + res->end = -1; + return AE_OK; + } + + res->start = port; + res->end = res->start + addr.address_length - 1; + + if (pci_remap_iospace(res, start) < 0) + return AE_OK; + + info->res_offset[info->res_num] = 0; + } else + info->res_offset[info->res_num] = addr.translation_offset; + + info->res_num++; + + return AE_OK; +} + +static void coalesce_windows(struct pci_root_info *info, unsigned long type) +{ + int i, j; + struct resource *res1, *res2; + + for (i = 0; i < info->res_num; i++) { + res1 = &info->res[i]; + if (!(res1->flags & type)) + continue; + + for (j = i + 1; j < info->res_num; j++) { + res2 = &info->res[j]; + if (!(res2->flags & type)) + continue; + + /* + * I don't like throwing away windows because then + * our resources no longer match the ACPI _CRS, but + * the kernel resource tree doesn't allow overlaps. + */ + if (resource_overlaps(res1, res2)) { + res2->start = min(res1->start, res2->start); + res2->end = max(res1->end, res2->end); + dev_info(&info->bridge->dev, + "host bridge window expanded to %pR; %pR ignored\n", + res2, res1); + res1->flags = 0; + } + } + } +} + +static void add_resources(struct pci_root_info *info, + struct list_head *resources) +{ + int i; + struct resource *res, *root, *conflict; + + coalesce_windows(info, IORESOURCE_MEM); + coalesce_windows(info, IORESOURCE_IO); + + for (i = 0; i < info->res_num; i++) { + res = &info->res[i]; + + if (res->flags & IORESOURCE_MEM) + root = &iomem_resource; + else if (res->flags & IORESOURCE_IO) + root = &ioport_resource; + else + continue; + + conflict = insert_resource_conflict(root, res); + if (conflict) + dev_info(&info->bridge->dev, + "ignoring host bridge window %pR (conflicts with %s %pR)\n", + res, conflict->name, conflict); + else + pci_add_resource_offset(resources, res, + info->res_offset[i]); + } +} + +static void free_pci_root_info_res(struct pci_root_info *info) +{ + kfree(info->res); + info->res = NULL; + kfree(info->res_offset); + info->res_offset = NULL; + info->res_num = 0; +} + +static void __release_pci_root_info(struct pci_root_info *info) +{ + int i; + struct resource *res; + + for (i = 0; i < info->res_num; i++) { + res = &info->res[i]; + + if (!res->parent) + continue; + + if (!(res->flags & (IORESOURCE_MEM | IORESOURCE_IO))) + continue; + + release_resource(res); + } + + free_pci_root_info_res(info); + + kfree(info); +} + +static void release_pci_root_info(struct pci_host_bridge *bridge) +{ + struct pci_root_info *info = bridge->release_data; + + __release_pci_root_info(info); +} + +static void probe_pci_root_info(struct pci_root_info *info, + struct acpi_device *device, + int busnum, int domain) +{ + size_t size; + + sprintf(info->name, "PCI Bus %04x:%02x", domain, busnum); + info->bridge = device; + + info->res_num = 0; + acpi_walk_resources(device->handle, METHOD_NAME__CRS, count_resource, + info); + if (!info->res_num) + return; + + size = sizeof(*info->res) * info->res_num; + info->res = kzalloc_node(size, GFP_KERNEL, info->sd.node); + if (!info->res) { + info->res_num = 0; + return; + } + + size = sizeof(*info->res_offset) * info->res_num; + info->res_num = 0; + info->res_offset = kzalloc_node(size, GFP_KERNEL, info->sd.node); + if (!info->res_offset) { + kfree(info->res); + info->res = NULL; + return; + } + + acpi_walk_resources(device->handle, METHOD_NAME__CRS, setup_resource, + info); +} + /* Root bridge scanning */ struct pci_bus *pci_acpi_scan_root(struct acpi_pci_root *root) { - return NULL; + struct acpi_device *device = root->device; + struct pci_mmcfg_region *mcfg; + struct pci_root_info *info; + int domain = root->segment; + int busnum = root->secondary.start; + LIST_HEAD(resources); + struct pci_bus *bus; + struct pci_sysdata *sd; + int node; + + /* we need mmconfig */ + mcfg = pci_mmconfig_lookup(domain, busnum); + if (!mcfg) { + pr_err("pci_bus %04x:%02x has no MCFG table\n", + domain, busnum); + return NULL; + } + if (mcfg->fixup) + (*mcfg->fixup)(root, mcfg); + + if (!raw_pci_ops) { + pr_err("ACPI: No raw PCI ops!\n"); + return NULL; + } + + if (domain && !pci_domains_supported) { + printk(KERN_WARNING "pci_bus %04x:%02x: " + "ignored (multiple domains not supported)\n", + domain, busnum); + return NULL; + } + + node = NUMA_NO_NODE; + + info = kzalloc_node(sizeof(*info), GFP_KERNEL, node); + if (!info) { + printk(KERN_WARNING "pci_bus %04x:%02x: " + "ignored (out of memory)\n", domain, busnum); + return NULL; + } + info->segment = domain; + info->start_bus = busnum; + info->end_bus = root->secondary.end; + + sd = &info->sd; + sd->domain = domain; + sd->node = node; + sd->companion = device; + + probe_pci_root_info(info, device, busnum, domain); + + /* insert busn res at first */ + pci_add_resource(&resources, &root->secondary); + + /* then _CRS resources */ + add_resources(info, &resources); + + bus = pci_create_root_bus(NULL, busnum, &pci_root_ops, sd, &resources); + if (bus) { + pci_scan_child_bus(bus); + pci_set_host_bridge_release(to_pci_host_bridge(bus->bridge), + release_pci_root_info, info); + } else { + pci_free_resource_list(&resources); + __release_pci_root_info(info); + } + + /* After the PCI-E bus has been walked and all devices discovered, + * configure any settings of the fabric that might be necessary. + */ + if (bus) { + struct pci_bus *child; + list_for_each_entry(child, &bus->children, node) + pcie_bus_configure_settings(child); + } + + if (bus && node != NUMA_NO_NODE) + dev_printk(KERN_DEBUG, &bus->dev, "on NUMA node %d\n", node); + + return bus; }
On Monday 10 November 2014 19:46:47 al.stone@linaro.org wrote:
The following patches are pretty much the same core as used for the Seattle topic branch. There are a few additional APM X-Gene specific patches.
These are not final by any stretch of the imagination. As with the Seattle patches, there is much work to be done for PCI (especially since the Mustang hardware is a very special case), and it is a known the the network card does not yet work properly.
So why commit these patches? Simply put, to record the current progress and make visible the current work on Mustang.
This kernel does boot and run, and PCI does seem to work. As noted, the NIC is not yet correct but this is being investigated and may be firmware related, not kernel.
Thanks for posting these. I think it is very useful to see the specific problems you run into when trying to run ACPI on some hardware that isn't designed against the SBSA or with ACPI in mind, and what kinds of workarounds would be necessary if we wanted to support it.
I've replied to the parts that I see as most problematic. My impression is that overall, it's too much of a hack, and we are better off by never merging any of the X-Gene specific patches from your series upstream.
There is a theoretical way to support this, depending on what your reasons are for doing this work: the firmware could implement an EL2 hypervisor that implements an SBSA compliant guest by faking a PCI mmconfig register set and a standard AHCI and UART, plus PSCI mode. The obvious downside of this would be the inability to run virtual machines.
Arnd
On 11/11/2014 07:27 AM, Arnd Bergmann wrote:
On Monday 10 November 2014 19:46:47 al.stone@linaro.org wrote:
The following patches are pretty much the same core as used for the Seattle topic branch. There are a few additional APM X-Gene specific patches.
These are not final by any stretch of the imagination. As with the Seattle patches, there is much work to be done for PCI (especially since the Mustang hardware is a very special case), and it is a known the the network card does not yet work properly.
So why commit these patches? Simply put, to record the current progress and make visible the current work on Mustang.
This kernel does boot and run, and PCI does seem to work. As noted, the NIC is not yet correct but this is being investigated and may be firmware related, not kernel.
Thanks for posting these. I think it is very useful to see the specific problems you run into when trying to run ACPI on some hardware that isn't designed against the SBSA or with ACPI in mind, and what kinds of workarounds would be necessary if we wanted to support it.
I've replied to the parts that I see as most problematic. My impression is that overall, it's too much of a hack, and we are better off by never merging any of the X-Gene specific patches from your series upstream.
There is a theoretical way to support this, depending on what your reasons are for doing this work: the firmware could implement an EL2 hypervisor that implements an SBSA compliant guest by faking a PCI mmconfig register set and a standard AHCI and UART, plus PSCI mode. The obvious downside of this would be the inability to run virtual machines.
Arnd
Hey, Arnd. I'm still sorting comments, but I have had several other things take precedence. So, my apologies for not having responded already. I will as soon as I can, though.
And yeah, I'm not convinced this should ever go upstream, predominantly because of PCI. My plan is to do one more pass of this as a topic in the Linaro acpi.git tree and then I'll likely let it rest so I can stay focused on servers.