Message ID | 20250410200202.2974062-5-superm1@kernel.org |
---|---|
State | New |
Headers | show |
Series | AMD Zen debugging documentation | expand |
On Thu, Apr 10, 2025 at 03:02:02PM -0500, Mario Limonciello wrote: > +static __init int print_s5_reset_status_mmio(void) > +{ > + void __iomem *addr; > + unsigned long value; > + int bit = -1; > + > + if (!cpu_feature_enabled(X86_FEATURE_ZEN)) > + return 0; > + > + addr = ioremap(FCH_PM_BASE + FCH_PM_S5_RESET_STATUS, sizeof(value)); > + if (!addr) > + return 0; newline. > + value = ioread32(addr); > + iounmap(addr); > + > + do { > + bit = find_next_bit(&value, BITS_PER_LONG, bit + 1); > + } while (!s5_reset_reason_txt[bit]); What's the idea here? The highest bit is the most fitting one? So why don't you do fls() or so? > + pr_info("x86/amd: Previous system reset reason [0x%08lx]: %s\n", > + value, s5_reset_reason_txt[bit]); What's guaranteeing that s5_reset_reason_txt[bit] is still set here? I'd suggest you check it again and never trust the hw because we'll be fixing a null ptr here at some point otherwise...
On 4/11/25 07:06, Borislav Petkov wrote: > On Thu, Apr 10, 2025 at 03:02:02PM -0500, Mario Limonciello wrote: >> +static __init int print_s5_reset_status_mmio(void) >> +{ >> + void __iomem *addr; >> + unsigned long value; >> + int bit = -1; >> + >> + if (!cpu_feature_enabled(X86_FEATURE_ZEN)) >> + return 0; >> + >> + addr = ioremap(FCH_PM_BASE + FCH_PM_S5_RESET_STATUS, sizeof(value)); >> + if (!addr) >> + return 0; > > newline. > >> + value = ioread32(addr); >> + iounmap(addr); >> + >> + do { >> + bit = find_next_bit(&value, BITS_PER_LONG, bit + 1); >> + } while (!s5_reset_reason_txt[bit]); > > What's the idea here? The highest bit is the most fitting one? > > So why don't you do fls() or so? The idea was to walk all the bits and pick the first one that has a string associated with it. I was finding that sometimes the reserved bits are set which would get you a NULL pointer deref. > >> + pr_info("x86/amd: Previous system reset reason [0x%08lx]: %s\n", >> + value, s5_reset_reason_txt[bit]); > > What's guaranteeing that s5_reset_reason_txt[bit] is still set here? > > I'd suggest you check it again and never trust the hw because we'll be fixing > a null ptr here at some point otherwise... > Right; I was worried about that too but find_next_bit() will return the size argument when it doesn't find anything. So that should be s5_reset_reason_txt[32] which has the "Unknown" string.
On Fri, Apr 11, 2025 at 07:12:24AM -0500, Mario Limonciello wrote: > The idea was to walk all the bits and pick the first one that has a string > associated with it. I was finding that sometimes the reserved bits are set > which would get you a NULL pointer deref. Uff, that needs a comment at least. But you can write it a lot simpler instead: for (i = 0; i <= ARRAY_SIZE(s5_reset_reason_txt); i++) { if (!(value & BIT(i))) continue; if (s5_reset_reason_txt[i]) break; } Simple loop, simple statements and all easy. :-) > Right; I was worried about that too but find_next_bit() will return the size > argument when it doesn't find anything. > > So that should be s5_reset_reason_txt[32] which has the "Unknown" string. Yeah, that definitely needs a comment above it.
On 4/11/25 07:50, Borislav Petkov wrote: > On Fri, Apr 11, 2025 at 07:12:24AM -0500, Mario Limonciello wrote: >> The idea was to walk all the bits and pick the first one that has a string >> associated with it. I was finding that sometimes the reserved bits are set >> which would get you a NULL pointer deref. > > Uff, that needs a comment at least. > > But you can write it a lot simpler instead: > > for (i = 0; i <= ARRAY_SIZE(s5_reset_reason_txt); i++) { > if (!(value & BIT(i))) > continue; > > if (s5_reset_reason_txt[i]) > break; > } > > Simple loop, simple statements and all easy. :-) > >> Right; I was worried about that too but find_next_bit() will return the size >> argument when it doesn't find anything. >> >> So that should be s5_reset_reason_txt[32] which has the "Unknown" string. > > Yeah, that definitely needs a comment above it. > Thanks; I'll take your simpler solution and leave a comment above the !(value & BIT(i)) check about skipping reserved bits.
* Mario Limonciello <superm1@kernel.org> wrote: > > > On 4/11/25 07:06, Borislav Petkov wrote: > > On Thu, Apr 10, 2025 at 03:02:02PM -0500, Mario Limonciello wrote: > > > +static __init int print_s5_reset_status_mmio(void) > > > +{ > > > + void __iomem *addr; > > > + unsigned long value; > > > + int bit = -1; > > > + > > > + if (!cpu_feature_enabled(X86_FEATURE_ZEN)) > > > + return 0; > > > + > > > + addr = ioremap(FCH_PM_BASE + FCH_PM_S5_RESET_STATUS, sizeof(value)); > > > + if (!addr) > > > + return 0; > > > > newline. > > > > > + value = ioread32(addr); > > > + iounmap(addr); > > > + > > > + do { > > > + bit = find_next_bit(&value, BITS_PER_LONG, bit + 1); > > > + } while (!s5_reset_reason_txt[bit]); > > > > What's the idea here? The highest bit is the most fitting one? > > > > So why don't you do fls() or so? > > The idea was to walk all the bits and pick the first one that has a string > associated with it. I was finding that sometimes the reserved bits are set > which would get you a NULL pointer deref. Would it be possible for firmware to set multiple bits with a text behind it? BTW: + [32] = "unknown", but BITS_PER_LONG is 64 on x86-64, not 32. How is that supposed to work? Anyway, in terms of robustness, it would be best to assume nothing about the structure of the bitmask, and do something straightforward like this: unsigned long value; int nr_reasons = 0; int bit = -1; ... /* Iterate on each bit in the 'value' mask: */ for (;;) { bit = find_next_bit(&value, BITS_PER_LONG, bit + 1); /* Reached the end of the word, no more bits: */ if (bit >= BITS_PER_LONG) { if (!nr_reasons) pr_info("x86/amd: Previous system reset reason [0x%08lx]: Unknown\n", value); break; } nr_reasons++; pr_info("x86/amd: Previous system reset reason [0x%08lx]: %s\n", value, s5_reset_reason_txt[bit]); } which prints out multiple bits as well, and does the right thing if no bit is found, without having to encode BITS_PER_LONG in the s5_reset_reason_txt[] array. And BTW: thanks for implementing this. :-) Thanks, Ingo
diff --git a/Documentation/admin-guide/amd/index.rst b/Documentation/admin-guide/amd/index.rst index 5a721ab4fe013..c888b192365c5 100644 --- a/Documentation/admin-guide/amd/index.rst +++ b/Documentation/admin-guide/amd/index.rst @@ -268,3 +268,45 @@ EPP Policy The ``energy_performance_preference`` sysfs file can be used to set a bias of efficiency or performance for a CPU. This has a direct relationship on the battery life when more heavily biased towards performance. + +Random reboot issues +==================== +When a random reboot occurs, the high-level reason for the reboot is stored +in a register that will persist onto the next boot. + +There are 6 classes of reasons for the reboot: + * Software induced + * Power state transition + * Pin induced + * Hardware induced + * Remote reset + * Internal CPU event + +.. csv-table:: + :header: "Bit", "Type", "Reason" + :align: left + + "0", "Pin", "thermal pin BP_THERMTRIP_L was tripped" + "1", "Pin", "power button was pressed for 4 seconds" + "2", "Pin", "shutdown pin was shorted" + "4", "Remote", "remote ASF power off command was received" + "9", "Internal", "internal CPU thermal limit was tripped" + "16", "Pin", "system reset pin BP_SYS_RST_L was tripped" + "17", "Software", "software issued PCI reset" + "18", "Software", "software wrote 0x4 to reset control register 0xCF9" + "19", "Software", "software wrote 0x6 to reset control register 0xCF9" + "20", "Software", "software wrote 0xE to reset control register 0xCF9" + "21", "Sleep", "ACPI power state transition occurred" + "22", "Pin", "keyboard reset pin KB_RST_L was asserted" + "23", "Internal", "internal CPU shutdown event occurred" + "24", "Hardware", "system failed to boot before failed boot timer expired" + "25", "Hardware", "hardware watchdog timer expired" + "26", "Remote", "remote ASF reset command was received" + "27", "Internal", "an uncorrected error caused a data fabric sync flood event" + "29", "Internal", "FCH and MP1 failed warm reset handshake" + "30", "Internal", "a parity error occurred" + "31", "Internal", "a software sync flood event occurred" + +This information is read by the kernel at bootup and is saved into the +kernel ring buffer. When a random reboot occurs this message can be helpful +to determine the next component to debug such an issue. diff --git a/arch/x86/include/asm/amd_node.h b/arch/x86/include/asm/amd_node.h index f4993201834ea..a945d146f1a77 100644 --- a/arch/x86/include/asm/amd_node.h +++ b/arch/x86/include/asm/amd_node.h @@ -20,6 +20,7 @@ #include <linux/pci.h> #define FCH_PM_BASE 0xFED80300 +#define FCH_PM_S5_RESET_STATUS 0xC0 #define MAX_AMD_NUM_NODES 8 #define AMD_NODE0_PCI_SLOT 0x18 diff --git a/arch/x86/kernel/cpu/amd.c b/arch/x86/kernel/cpu/amd.c index 79569f72b8ee5..ddb17f0ad580e 100644 --- a/arch/x86/kernel/cpu/amd.c +++ b/arch/x86/kernel/cpu/amd.c @@ -9,6 +9,7 @@ #include <linux/sched/clock.h> #include <linux/random.h> #include <linux/topology.h> +#include <asm/amd_node.h> #include <asm/processor.h> #include <asm/apic.h> #include <asm/cacheinfo.h> @@ -1231,3 +1232,53 @@ void amd_check_microcode(void) if (cpu_feature_enabled(X86_FEATURE_ZEN2)) on_each_cpu(zenbleed_check_cpu, NULL, 1); } + +static const char * const s5_reset_reason_txt[] = { + [0] = "thermal pin BP_THERMTRIP_L was tripped", + [1] = "power button was pressed for 4 seconds", + [2] = "shutdown pin was shorted", + [4] = "remote ASF power off command was received", + [9] = "internal CPU thermal limit was tripped", + [16] = "system reset pin BP_SYS_RST_L was tripped", + [17] = "software issued PCI reset", + [18] = "software wrote 0x4 to reset control register 0xCF9", + [19] = "software wrote 0x6 to reset control register 0xCF9", + [20] = "software wrote 0xE to reset control register 0xCF9", + [21] = "ACPI power state transition occurred", + [22] = "keyboard reset pin KB_RST_L was asserted", + [23] = "internal CPU shutdown event occurred", + [24] = "system failed to boot before failed boot timer expired", + [25] = "hardware watchdog timer expired", + [26] = "remote ASF reset command was received", + [27] = "an uncorrected error caused a data fabric sync flood event", + [29] = "FCH and MP1 failed warm reset handshake", + [30] = "a parity error occurred", + [31] = "a software sync flood event occurred", + [32] = "unknown", +}; + +static __init int print_s5_reset_status_mmio(void) +{ + void __iomem *addr; + unsigned long value; + int bit = -1; + + if (!cpu_feature_enabled(X86_FEATURE_ZEN)) + return 0; + + addr = ioremap(FCH_PM_BASE + FCH_PM_S5_RESET_STATUS, sizeof(value)); + if (!addr) + return 0; + value = ioread32(addr); + iounmap(addr); + + do { + bit = find_next_bit(&value, BITS_PER_LONG, bit + 1); + } while (!s5_reset_reason_txt[bit]); + + pr_info("x86/amd: Previous system reset reason [0x%08lx]: %s\n", + value, s5_reset_reason_txt[bit]); + + return 0; +} +late_initcall(print_s5_reset_status_mmio);