Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

arm64: kernel: implement ACPI parking protocol

The SBBR and ACPI specifications allow ACPI based systems that do not
implement PSCI (eg systems with no EL3) to boot through the ACPI parking
protocol specification[1].

This patch implements the ACPI parking protocol CPU operations, and adds
code that eases parsing the parking protocol data structures to the
ARM64 SMP initializion carried out at the same time as cpus enumeration.

To wake-up the CPUs from the parked state, this patch implements a
wakeup IPI for ARM64 (ie arch_send_wakeup_ipi_mask()) that mirrors the
ARM one, so that a specific IPI is sent for wake-up purpose in order
to distinguish it from other IPI sources.

Given the current ACPI MADT parsing API, the patch implements a glue
layer that helps passing MADT GICC data structure from SMP initialization
code to the parking protocol implementation somewhat overriding the CPU
operations interfaces. This to avoid creating a completely trasparent
DT/ACPI CPU operations layer that would require creating opaque
structure handling for CPUs data (DT represents CPU through DT nodes, ACPI
through static MADT table entries), which seems overkill given that ACPI
on ARM64 mandates only two booting protocols (PSCI and parking protocol),
so there is no need for further protocol additions.

Based on the original work by Mark Salter <msalter@redhat.com>

[1] https://acpica.org/sites/acpica/files/MP%20Startup%20for%20ARM%20platforms.docx

Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
Tested-by: Loc Ho <lho@apm.com>
Cc: Will Deacon <will.deacon@arm.com>
Cc: Hanjun Guo <hanjun.guo@linaro.org>
Cc: Sudeep Holla <sudeep.holla@arm.com>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Mark Salter <msalter@redhat.com>
Cc: Al Stone <ahs3@redhat.com>
[catalin.marinas@arm.com: Added WARN_ONCE(!acpi_parking_protocol_valid() on the IPI]
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>

authored by

Lorenzo Pieralisi and committed by
Catalin Marinas
5e89c55e 068a17a5

+242 -6
+9
arch/arm64/Kconfig
··· 756 756 757 757 menu "Boot options" 758 758 759 + config ARM64_ACPI_PARKING_PROTOCOL 760 + bool "Enable support for the ARM64 ACPI parking protocol" 761 + depends on ACPI 762 + help 763 + Enable support for the ARM64 ACPI parking protocol. If disabled 764 + the kernel will not allow booting through the ARM64 ACPI parking 765 + protocol even if the corresponding data is present in the ACPI 766 + MADT table. 767 + 759 768 config CMDLINE 760 769 string "Default kernel command string" 761 770 default ""
+18 -1
arch/arm64/include/asm/acpi.h
··· 87 87 static inline void acpi_init_cpus(void) { } 88 88 #endif /* CONFIG_ACPI */ 89 89 90 + #ifdef CONFIG_ARM64_ACPI_PARKING_PROTOCOL 91 + bool acpi_parking_protocol_valid(int cpu); 92 + void __init 93 + acpi_set_mailbox_entry(int cpu, struct acpi_madt_generic_interrupt *processor); 94 + #else 95 + static inline bool acpi_parking_protocol_valid(int cpu) { return false; } 96 + static inline void 97 + acpi_set_mailbox_entry(int cpu, struct acpi_madt_generic_interrupt *processor) 98 + {} 99 + #endif 100 + 90 101 static inline const char *acpi_get_enable_method(int cpu) 91 102 { 92 - return acpi_psci_present() ? "psci" : NULL; 103 + if (acpi_psci_present()) 104 + return "psci"; 105 + 106 + if (acpi_parking_protocol_valid(cpu)) 107 + return "parking-protocol"; 108 + 109 + return NULL; 93 110 } 94 111 95 112 #ifdef CONFIG_ACPI_APEI
+1 -1
arch/arm64/include/asm/hardirq.h
··· 20 20 #include <linux/threads.h> 21 21 #include <asm/irq.h> 22 22 23 - #define NR_IPI 5 23 + #define NR_IPI 6 24 24 25 25 typedef struct { 26 26 unsigned int __softirq_pending;
+9
arch/arm64/include/asm/smp.h
··· 64 64 extern void arch_send_call_function_single_ipi(int cpu); 65 65 extern void arch_send_call_function_ipi_mask(const struct cpumask *mask); 66 66 67 + #ifdef CONFIG_ARM64_ACPI_PARKING_PROTOCOL 68 + extern void arch_send_wakeup_ipi_mask(const struct cpumask *mask); 69 + #else 70 + static inline void arch_send_wakeup_ipi_mask(const struct cpumask *mask) 71 + { 72 + BUILD_BUG(); 73 + } 74 + #endif 75 + 67 76 extern int __cpu_disable(void); 68 77 69 78 extern void __cpu_die(unsigned int cpu);
+1
arch/arm64/kernel/Makefile
··· 41 41 arm64-obj-$(CONFIG_PCI) += pci.o 42 42 arm64-obj-$(CONFIG_ARMV8_DEPRECATED) += armv8_deprecated.o 43 43 arm64-obj-$(CONFIG_ACPI) += acpi.o 44 + arm64-obj-$(CONFIG_ARM64_ACPI_PARKING_PROTOCOL) += acpi_parking_protocol.o 44 45 arm64-obj-$(CONFIG_PARAVIRT) += paravirt.o 45 46 46 47 obj-y += $(arm64-obj-y) vdso/
+153
arch/arm64/kernel/acpi_parking_protocol.c
··· 1 + /* 2 + * ARM64 ACPI Parking Protocol implementation 3 + * 4 + * Authors: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> 5 + * Mark Salter <msalter@redhat.com> 6 + * 7 + * This program is free software; you can redistribute it and/or modify 8 + * it under the terms of the GNU General Public License version 2 as 9 + * published by the Free Software Foundation. 10 + * 11 + * This program is distributed in the hope that it will be useful, 12 + * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 + * GNU General Public License for more details. 15 + * 16 + * You should have received a copy of the GNU General Public License 17 + * along with this program. If not, see <http://www.gnu.org/licenses/>. 18 + */ 19 + #include <linux/acpi.h> 20 + #include <linux/types.h> 21 + 22 + #include <asm/cpu_ops.h> 23 + 24 + struct cpu_mailbox_entry { 25 + phys_addr_t mailbox_addr; 26 + u8 version; 27 + u8 gic_cpu_id; 28 + }; 29 + 30 + static struct cpu_mailbox_entry cpu_mailbox_entries[NR_CPUS]; 31 + 32 + void __init acpi_set_mailbox_entry(int cpu, 33 + struct acpi_madt_generic_interrupt *p) 34 + { 35 + struct cpu_mailbox_entry *cpu_entry = &cpu_mailbox_entries[cpu]; 36 + 37 + cpu_entry->mailbox_addr = p->parked_address; 38 + cpu_entry->version = p->parking_version; 39 + cpu_entry->gic_cpu_id = p->cpu_interface_number; 40 + } 41 + 42 + bool acpi_parking_protocol_valid(int cpu) 43 + { 44 + struct cpu_mailbox_entry *cpu_entry = &cpu_mailbox_entries[cpu]; 45 + 46 + return cpu_entry->mailbox_addr && cpu_entry->version; 47 + } 48 + 49 + static int acpi_parking_protocol_cpu_init(unsigned int cpu) 50 + { 51 + pr_debug("%s: ACPI parked addr=%llx\n", __func__, 52 + cpu_mailbox_entries[cpu].mailbox_addr); 53 + 54 + return 0; 55 + } 56 + 57 + static int acpi_parking_protocol_cpu_prepare(unsigned int cpu) 58 + { 59 + return 0; 60 + } 61 + 62 + struct parking_protocol_mailbox { 63 + __le32 cpu_id; 64 + __le32 reserved; 65 + __le64 entry_point; 66 + }; 67 + 68 + static int acpi_parking_protocol_cpu_boot(unsigned int cpu) 69 + { 70 + struct cpu_mailbox_entry *cpu_entry = &cpu_mailbox_entries[cpu]; 71 + struct parking_protocol_mailbox __iomem *mailbox; 72 + __le32 cpu_id; 73 + 74 + /* 75 + * Map mailbox memory with attribute device nGnRE (ie ioremap - 76 + * this deviates from the parking protocol specifications since 77 + * the mailboxes are required to be mapped nGnRnE; the attribute 78 + * discrepancy is harmless insofar as the protocol specification 79 + * is concerned). 80 + * If the mailbox is mistakenly allocated in the linear mapping 81 + * by FW ioremap will fail since the mapping will be prevented 82 + * by the kernel (it clashes with the linear mapping attributes 83 + * specifications). 84 + */ 85 + mailbox = ioremap(cpu_entry->mailbox_addr, sizeof(*mailbox)); 86 + if (!mailbox) 87 + return -EIO; 88 + 89 + cpu_id = readl_relaxed(&mailbox->cpu_id); 90 + /* 91 + * Check if firmware has set-up the mailbox entry properly 92 + * before kickstarting the respective cpu. 93 + */ 94 + if (cpu_id != ~0U) { 95 + iounmap(mailbox); 96 + return -ENXIO; 97 + } 98 + 99 + /* 100 + * We write the entry point and cpu id as LE regardless of the 101 + * native endianness of the kernel. Therefore, any boot-loaders 102 + * that read this address need to convert this address to the 103 + * Boot-Loader's endianness before jumping. 104 + */ 105 + writeq_relaxed(__pa(secondary_entry), &mailbox->entry_point); 106 + writel_relaxed(cpu_entry->gic_cpu_id, &mailbox->cpu_id); 107 + 108 + arch_send_wakeup_ipi_mask(cpumask_of(cpu)); 109 + 110 + iounmap(mailbox); 111 + 112 + return 0; 113 + } 114 + 115 + static void acpi_parking_protocol_cpu_postboot(void) 116 + { 117 + int cpu = smp_processor_id(); 118 + struct cpu_mailbox_entry *cpu_entry = &cpu_mailbox_entries[cpu]; 119 + struct parking_protocol_mailbox __iomem *mailbox; 120 + __le64 entry_point; 121 + 122 + /* 123 + * Map mailbox memory with attribute device nGnRE (ie ioremap - 124 + * this deviates from the parking protocol specifications since 125 + * the mailboxes are required to be mapped nGnRnE; the attribute 126 + * discrepancy is harmless insofar as the protocol specification 127 + * is concerned). 128 + * If the mailbox is mistakenly allocated in the linear mapping 129 + * by FW ioremap will fail since the mapping will be prevented 130 + * by the kernel (it clashes with the linear mapping attributes 131 + * specifications). 132 + */ 133 + mailbox = ioremap(cpu_entry->mailbox_addr, sizeof(*mailbox)); 134 + if (!mailbox) 135 + return; 136 + 137 + entry_point = readl_relaxed(&mailbox->entry_point); 138 + /* 139 + * Check if firmware has cleared the entry_point as expected 140 + * by the protocol specification. 141 + */ 142 + WARN_ON(entry_point); 143 + 144 + iounmap(mailbox); 145 + } 146 + 147 + const struct cpu_operations acpi_parking_protocol_ops = { 148 + .name = "parking-protocol", 149 + .cpu_init = acpi_parking_protocol_cpu_init, 150 + .cpu_prepare = acpi_parking_protocol_cpu_prepare, 151 + .cpu_boot = acpi_parking_protocol_cpu_boot, 152 + .cpu_postboot = acpi_parking_protocol_cpu_postboot 153 + };
+23 -4
arch/arm64/kernel/cpu_ops.c
··· 25 25 #include <asm/smp_plat.h> 26 26 27 27 extern const struct cpu_operations smp_spin_table_ops; 28 + extern const struct cpu_operations acpi_parking_protocol_ops; 28 29 extern const struct cpu_operations cpu_psci_ops; 29 30 30 31 const struct cpu_operations *cpu_ops[NR_CPUS]; 31 32 32 - static const struct cpu_operations *supported_cpu_ops[] __initconst = { 33 + static const struct cpu_operations *dt_supported_cpu_ops[] __initconst = { 33 34 &smp_spin_table_ops, 35 + &cpu_psci_ops, 36 + NULL, 37 + }; 38 + 39 + static const struct cpu_operations *acpi_supported_cpu_ops[] __initconst = { 40 + #ifdef CONFIG_ARM64_ACPI_PARKING_PROTOCOL 41 + &acpi_parking_protocol_ops, 42 + #endif 34 43 &cpu_psci_ops, 35 44 NULL, 36 45 }; 37 46 38 47 static const struct cpu_operations * __init cpu_get_ops(const char *name) 39 48 { 40 - const struct cpu_operations **ops = supported_cpu_ops; 49 + const struct cpu_operations **ops; 50 + 51 + ops = acpi_disabled ? dt_supported_cpu_ops : acpi_supported_cpu_ops; 41 52 42 53 while (*ops) { 43 54 if (!strcmp(name, (*ops)->name)) ··· 86 75 } 87 76 } else { 88 77 enable_method = acpi_get_enable_method(cpu); 89 - if (!enable_method) 90 - pr_err("Unsupported ACPI enable-method\n"); 78 + if (!enable_method) { 79 + /* 80 + * In ACPI systems the boot CPU does not require 81 + * checking the enable method since for some 82 + * boot protocol (ie parking protocol) it need not 83 + * be initialized. Don't warn spuriously. 84 + */ 85 + if (cpu != 0) 86 + pr_err("Unsupported ACPI enable-method\n"); 87 + } 91 88 } 92 89 93 90 return enable_method;
+28
arch/arm64/kernel/smp.c
··· 70 70 IPI_CPU_STOP, 71 71 IPI_TIMER, 72 72 IPI_IRQ_WORK, 73 + IPI_WAKEUP 73 74 }; 74 75 75 76 /* ··· 444 443 /* map the logical cpu id to cpu MPIDR */ 445 444 cpu_logical_map(cpu_count) = hwid; 446 445 446 + /* 447 + * Set-up the ACPI parking protocol cpu entries 448 + * while initializing the cpu_logical_map to 449 + * avoid parsing MADT entries multiple times for 450 + * nothing (ie a valid cpu_logical_map entry should 451 + * contain a valid parking protocol data set to 452 + * initialize the cpu if the parking protocol is 453 + * the only available enable method). 454 + */ 455 + acpi_set_mailbox_entry(cpu_count, processor); 456 + 447 457 cpu_count++; 448 458 } 449 459 ··· 637 625 S(IPI_CPU_STOP, "CPU stop interrupts"), 638 626 S(IPI_TIMER, "Timer broadcast interrupts"), 639 627 S(IPI_IRQ_WORK, "IRQ work interrupts"), 628 + S(IPI_WAKEUP, "CPU wake-up interrupts"), 640 629 }; 641 630 642 631 static void smp_cross_call(const struct cpumask *target, unsigned int ipinr) ··· 680 667 { 681 668 smp_cross_call(cpumask_of(cpu), IPI_CALL_FUNC); 682 669 } 670 + 671 + #ifdef CONFIG_ARM64_ACPI_PARKING_PROTOCOL 672 + void arch_send_wakeup_ipi_mask(const struct cpumask *mask) 673 + { 674 + smp_cross_call(mask, IPI_WAKEUP); 675 + } 676 + #endif 683 677 684 678 #ifdef CONFIG_IRQ_WORK 685 679 void arch_irq_work_raise(void) ··· 762 742 irq_enter(); 763 743 irq_work_run(); 764 744 irq_exit(); 745 + break; 746 + #endif 747 + 748 + #ifdef CONFIG_ARM64_ACPI_PARKING_PROTOCOL 749 + case IPI_WAKEUP: 750 + WARN_ONCE(!acpi_parking_protocol_valid(cpu), 751 + "CPU%u: Wake-up IPI outside the ACPI parking protocol\n", 752 + cpu); 765 753 break; 766 754 #endif 767 755