cmd: pstore: find the zones where Linux puts them
cover cmd: pstore: find the zones where Linux puts them
pstore display and pstore save look for the records where they expect the Linux ramoops driver to have put them. This series makes them use the layout of ramoops_probe() in Linux, including the zone order that changed in Linux 6.2. Each fix is followed by a test in test_pstore.py that fails without it.
Patch 1 checks that the buffer for the records is allocated. With a record size larger than the malloc pool, pstore display and pstore save copied the first valid record to address 0. Patch 2 sets such a size.
Patch 3 computes the zones once, as Linux does, for both commands. Linux rounds each size down to a power of two, and splits the memory left for the dump records into as many zones as there is room for records, so a dump zone is larger than the record size when memory is left over. U-Boot used the sizes as given and zones of the record size, and pstore display and pstore save did not even agree on where the console zone starts. With a record size of 0, Linux makes no dump zones, while both commands looped forever. pstore set now checks the rounded sizes, in 64 bits, and pstore display dump <n> no longer reads past the dump zones. The default sizes are not affected by any of this. This is the layout of Linux 4.10 and later, with the dump zone size rounded down to an even number as in Linux 6.8 and the stable kernels that got the same change; the commit message says how other kernels differ. Patch 4 checks such layouts, a record size of 0 and the checks in pstore set.
Patch 5 handles the order of the user space (pmsg) and ftrace zones. Linux 6.2 moved the user space zone before the ftrace zone, so with current kernels U-Boot shows and saves the user space record as the ftrace one and does not find the ftrace record, the default sizes included. The new order is now the default, and the old one is used when the zone after the console zone has an ftrace signature, 'DBGC' XORed with LINUX_VERSION_CODE. Patch 6 writes the two records in both orders, and checks that a user space header without a signature, next to ftrace data that looks like a user space record, does not mislead that check.
Without patch 1, the test in patch 2 crashes U-Boot. Without patch 3, pstore display loops forever in the second test of patch 4, which test.py treats as a lab failure that stops the whole run instead of failing one test.
Each commit builds for sandbox and sandbox64, and cmd/pstore.c builds without warnings with W=1 for pinephone (arm64), r8a78000_ironhide_cm33 (32-bit) and rpi_4_32b with CMD_PSTORE enabled (32-bit with a 64-bit phys_size_t, which also links). All pstore tests pass on sandbox.
Aristo Chen (6):
cmd: pstore: check that the buffer for the records is allocated
test: pstore: check that a buffer allocation failure is reported
cmd: pstore: lay out the zones as Linux does
test: pstore: check that the zones are found where Linux puts them
cmd: pstore: find the user space zone where Linux 6.2 puts it
test: pstore: check the ftrace and user zones in both orders
cmd/pstore.c | 203 +++++++++++++++++++++++++---------- test/py/tests/test_pstore.py | 196 ++++++++++++++++++++++++++++++++- 2 files changed, 341 insertions(+), 58 deletions(-)
Signature · 1 line
2.43.0
PATCH 1/6 cmd: pstore: check that the buffer for the records is allocated
pstore display and pstore save copy each record into a buffer as large as the largest record size set, without checking that it could be allocated. With a size larger than the malloc pool, the first valid record is copied to address 0.
Report the error instead.
--- cmd/pstore.c | 10 ++++++++++ 1 file changed, 10 insertions(+)
cmd/pstore.c +10 -0
@@ -278,6 +278,11 @@ static int pstore_display(struct cmd_tbl *cmdtp, int flag, int argc,pstore_init_buffer_size (); buffer =malloc_cache_aligned (buffer_size); +if (!buffer) { +printf ("Cannot allocate %u bytes for the records\n" , + buffer_size); +return CMD_RET_FAILURE ; + }if (type ==PSTORE_TYPE_DMESG || type ==PSTORE_TYPE_ALL ) { ptr = pstore_addr;
@@ -389,6 +394,11 @@ static int pstore_save(struct cmd_tbl *cmdtp, int flag, int argc,pstore_init_buffer_size (); buffer =malloc_cache_aligned (buffer_size); +if (!buffer) { +printf ("Cannot allocate %u bytes for the records\n" , + buffer_size); +return CMD_RET_FAILURE ; + }sprintf (addr,"0x%p" , buffer); save_argv[0 ] = argv[0 ];
Signature · 1 line
2.43.0
PATCH 2/6 test: pstore: check that a buffer allocation failure is reported
In reply to Aristo ChenSet a record size larger than the malloc pool, write a dump record and check that pstore display and pstore save report that they cannot allocate their buffer and write no file. Without the allocation check, U-Boot crashes on the first record.
--- test/py/tests/test_pstore.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+)
test/py/tests/test_pstore.py +34 -0
@@ -7,6 +7,7 @@ import utils import os import tempfile import shutil +import struct PSTORE_ADDR=0x3000000 PSTORE_LENGTH=0x100000
@@ -49,6 +50,14 @@ def write_header(ubman, addr, sig, start, size): 'mw.l 0x%x 0x%x' % (addr + 4, start), 'mw.l 0x%x 0x%x' % (addr + 8, size)]) +def write_record(ubman, addr, sig, data): + """Write a record holding data at addr""" + + with tempfile.NamedTemporaryFile() as f: + f.write(struct.pack('<III', sig, 0, len(data)) + data) + f.flush() + ubman.run_command('host load hostfs - 0x%x %s' % (addr, f.name)) + @pytest.mark.buildconfigspec('cmd_pstore') def test_pstore_display_all_records(ubman): """Test that pstore displays all records."""
@@ -163,3 +172,28 @@ def test_pstore_cut_dump_header(ubman): assert(not os.path.exists('%s/dmesg-ramoops-1' % (outdir))) shutil.rmtree(outdir) + [email protected]('cmd_pstore') +def test_pstore_buffer_too_large(ubman): + """Test that pstore reports a buffer that it cannot allocate.""" + + # A record size larger than the malloc pool + malloc_len = int(ubman.config.buildconfig['config_sys_malloc_len'], 0) + size = 1 << malloc_len.bit_length() + + ubman.run_command('') + try: + ubman.run_command('pstore set 0x%x 0x%x 0x%x 0 0 0' % + (PSTORE_ADDR, size, size)) + write_record(ubman, PSTORE_ADDR, PSTORE_SIG, b'====1.0-D\ndump\n') + + response = ubman.run_command('pstore display') + assert('Cannot allocate' in response) + + with tempfile.TemporaryDirectory() as outdir: + response = ubman.run_command('pstore save hostfs - %s' % outdir) + assert('Cannot allocate' in response) + assert(not os.listdir(outdir)) + finally: + # Restore the default record sizes + ubman.restart_uboot()
Signature · 1 line
2.43.0
PATCH 3/6 cmd: pstore: lay out the zones as Linux does
In reply to Aristo Chenpstore display and pstore save find the records where Linux puts them only when all sizes are powers of two and the dump records fill the memory left for them, as with the default sizes:
- ramoops_probe() in Linux rounds each size down to a power of two. U-Boot used the sizes as given. pstore set also checked their sum without rounding, in 32 bits, so it rejected some sizes that Linux accepts and accepted some whose sum wraps around.
- Linux splits the memory left for the dump records into as many zones as there is room for records, so a zone is larger than the record size when memory is left over. U-Boot used zones of the record size, and its two commands did not even agree on where the console zone
starts: pstore display took the end of the memory for the dump records, pstore save the end of its last dump zone, which can lie past the end of the PStore memory.
- With a record size of 0, Linux makes no dump zones. Both commands looped forever.
Compute the zones once, as Linux does, use them in both commands, and check the rounded sizes in pstore set, in 64 bits. As a dump zone can now be larger than the record size, size the buffer from the zones on each call instead of keeping a size from the first call. Also, pstore display dump <n> now reads nothing when there is no dump zone n, instead of reading past the dump zones.
This is the layout of Linux 4.10 and later, with the dump zone size rounded down to an even number as in Linux 6.8 and the stable kernels that got the same change. The other kernels only differ when the dump records do not fill the memory left for them: Linux 4.9 and older use zones of the record size, and Linux 4.10 to 6.7 without that change do not round an odd zone size down.
--- cmd/pstore.c | 176 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 114 insertions(+), 62 deletions(-)
cmd/pstore.c +114 -62
@@ -5,6 +5,7 @@#include <config.h> #include <command.h> +#include <div64.h> #include <fdtdec.h> #include <fs.h> #include <log.h>
@@ -12,6 +13,7 @@#include <memalign.h> #include <part.h> #include <fdt_support.h> +#include <linux/log2.h> struct persistent_ram_buffer {u32 sig;
@@ -20,6 +22,34 @@ struct persistent_ram_buffer {u8 data[0 ]; }; +/** +* struct pstore_zone - Zone of the PStore memory +* @addr: Address of the zone +* @size: Size of the zone, record header included +*/ +struct pstore_zone { +phys_addr_t addr; +u32 size; +}; + +/** +* struct pstore_zones - Zones of the PStore memory +* @dump: First dump zone, the others follow it with the same size +* @dump_count: Number of dump zones +* @dump_end: End of the dump zones +* @console: Console zone +* @ftrace: FTrace zone +* @pmsg: User space zone +*/ +struct pstore_zones { +struct pstore_zone dump; +u32 dump_count; +phys_addr_t dump_end; +struct pstore_zone console; +struct pstore_zone ftrace; +struct pstore_zone pmsg; +}; +#define PERSISTENT_RAM_SIG (0x43474244)/* DBGC */ #define RAMOOPS_KERNMSG_HDR "===="
@@ -36,7 +66,6 @@ static unsigned int pstore_console_size = CONFIG_CMD_PSTORE_CONSOLE_SIZE;static unsigned int pstore_ftrace_size =CONFIG_CMD_PSTORE_FTRACE_SIZE ;static unsigned int pstore_pmsg_size =CONFIG_CMD_PSTORE_PMSG_SIZE ;static unsigned int pstore_ecc_size =CONFIG_CMD_PSTORE_ECC_SIZE ; -static unsigned int buffer_size;/** * pstore_read_kmsg_hdr() - Check kernel header and get compression flag if
@@ -123,24 +152,52 @@ static u32 pstore_get_buffer(u32 sig, phys_addr_t buffer, u32 size, char *dest) }/** -* pstore_init_buffer_size() - Init buffer size to largest record size +* pstore_round_size() - Round a zone size down to a power of two +* @size: Zone size, can be 0 * -* Records, console, FTrace and user logs can use different buffer sizes. -* This function allows to retrieve the biggest one. +* Return: @size rounded down to a power of two, or 0 if @size is 0 */ -static void pstore_init_buffer_size (void ) +static u32 pstore_round_size (u32 size) { -if (pstore_record_size > buffer_size) - buffer_size = pstore_record_size; - -if (pstore_console_size > buffer_size) - buffer_size = pstore_console_size; - -if (pstore_ftrace_size > buffer_size) - buffer_size = pstore_ftrace_size; +return size ?rounddown_pow_of_two (size) :0 ; +} -if (pstore_pmsg_size > buffer_size) - buffer_size = pstore_pmsg_size; +/** +* pstore_get_zones() - Find the zones where Linux keeps the records +* @zones: Returns the zones +* +* Lay out the zones like ramoops_probe() in Linux: round each size down to a +* power of two, split the memory left for the dump records into as many +* zones as there is room for records, each rounded down to an even size, then +* put the console, FTrace and user space zones after them. +* +* Return: size of the largest zone, which a buffer needs for any record +*/ +static u32 pstore_get_zones (struct pstore_zones *zones) +{ +u32 record_size =pstore_round_size (pstore_record_size); +phys_size_t dump_size; +u32 count; + + zones->console.size =pstore_round_size (pstore_console_size); + zones->ftrace.size =pstore_round_size (pstore_ftrace_size); + zones->pmsg.size =pstore_round_size (pstore_pmsg_size); + + dump_size = pstore_length - zones->console.size - zones->ftrace.size - + zones->pmsg.size; + count = record_size ?lldiv (dump_size, record_size) :0 ; + zones->dump_count = count; + zones->dump.addr = pstore_addr; + zones->dump.size = count ?ALIGN_DOWN (lldiv (dump_size, count),2 ) :0 ; + zones->dump_end = zones->dump.addr + + (phys_size_t )count * zones->dump.size; + + zones->console.addr = zones->dump_end; + zones->ftrace.addr = zones->console.addr + zones->console.size; + zones->pmsg.addr = zones->ftrace.addr + zones->ftrace.size; + +return max (max (zones->dump.size, zones->console.size), +max (zones->ftrace.size, zones->pmsg.size)); }/**
@@ -196,11 +253,11 @@ static int pstore_set(struct cmd_tbl *cmdtp, int flag, int argc,if (argc >7 ) pstore_ecc_size =hextoul (argv[7 ],NULL ); -/* Size the buffer again for the new record sizes on next use */ - buffer_size =0 ; - -if (pstore_length < (pstore_record_size + pstore_console_size - + pstore_ftrace_size + pstore_pmsg_size)) { +/* As Linux, use the sizes rounded down to powers of two */ +if (pstore_length < (u64 )pstore_round_size (pstore_record_size) + +pstore_round_size (pstore_console_size) + +pstore_round_size (pstore_ftrace_size) + +pstore_round_size (pstore_pmsg_size)) {printf ("pstore <len> should be larger than the sum of all records sizes\n" ); pstore_length =0 ; }
@@ -250,6 +307,7 @@ static int pstore_display(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]) {int type =PSTORE_TYPE_ALL ; +struct pstore_zones zones;phys_addr_t ptr;char *buffer;u32 size;
@@ -274,31 +332,33 @@ static int pstore_display(struct cmd_tbl *cmdtp, int flag, int argc,return CMD_RET_USAGE ; } -if (buffer_size ==0 ) -pstore_init_buffer_size (); - - buffer =malloc_cache_aligned (buffer_size); + size =pstore_get_zones (&zones); + buffer =malloc_cache_aligned (size);if (!buffer) { -printf ("Cannot allocate %u bytes for the records\n" , - buffer_size); +printf ("Cannot allocate %u bytes for the records\n" , size);return CMD_RET_FAILURE ; }if (type ==PSTORE_TYPE_DMESG || type ==PSTORE_TYPE_ALL ) { - ptr = pstore_addr; -phys_addr_t ptr_end = ptr + pstore_length - pstore_pmsg_size - - pstore_ftrace_size - pstore_console_size; + ptr = zones.dump.addr; +phys_addr_t ptr_end = zones.dump_end;if (argc >2 ) { - ptr +=dectoul (argv[2 ],NULL ) - * pstore_record_size; - ptr_end = ptr + pstore_record_size; +ulong index =dectoul (argv[2 ],NULL ); + +/* Only read that dump zone, if there is one */ +if (index < zones.dump_count) { + ptr += (phys_size_t )index * zones.dump.size; + ptr_end = ptr + zones.dump.size; + }else { + ptr_end = ptr; + } }while (ptr < ptr_end) { size =pstore_get_buffer (PERSISTENT_RAM_SIG , ptr, - pstore_record_size, buffer); - ptr += pstore_record_size; + zones.dump.size, buffer); + ptr += zones.dump.size;if (size ==0 )continue ;
@@ -321,30 +381,26 @@ static int pstore_display(struct cmd_tbl *cmdtp, int flag, int argc, }if (type ==PSTORE_TYPE_CONSOLE || type ==PSTORE_TYPE_ALL ) { - ptr = pstore_addr + pstore_length - pstore_pmsg_size - - pstore_ftrace_size - pstore_console_size; - size =pstore_get_buffer (PERSISTENT_RAM_SIG , ptr, - pstore_console_size, buffer); + size =pstore_get_buffer (PERSISTENT_RAM_SIG , zones.console.addr, + zones.console.size, buffer);if (size !=0 )pstore_print_buffer ("Console" , buffer, size); }if (type ==PSTORE_TYPE_FTRACE || type ==PSTORE_TYPE_ALL ) { - ptr = pstore_addr + pstore_length - pstore_pmsg_size - - pstore_ftrace_size;/* The FTrace record(s) uses LINUX_VERSION_CODE ^ 'DBGC' * signature, pass 0 to pstore_get_buffer to prevent * checking it */ - size =pstore_get_buffer (0 , ptr, pstore_ftrace_size, buffer); + size =pstore_get_buffer (0 , zones.ftrace.addr, + zones.ftrace.size, buffer);if (size !=0 )pstore_print_buffer ("FTrace" , buffer, size); }if (type ==PSTORE_TYPE_PMSG || type ==PSTORE_TYPE_ALL ) { - ptr = pstore_addr + pstore_length - pstore_pmsg_size; - size =pstore_get_buffer (PERSISTENT_RAM_SIG , ptr, - pstore_pmsg_size, buffer); + size =pstore_get_buffer (PERSISTENT_RAM_SIG , zones.pmsg.addr, + zones.pmsg.size, buffer);if (size !=0 )pstore_print_buffer ("User" , buffer, size); }
@@ -372,6 +428,7 @@ static int pstore_display(struct cmd_tbl *cmdtp, int flag, int argc,static int pstore_save (struct cmd_tbl *cmdtp,int flag,int argc,char *const argv[]) { +struct pstore_zones zones;phys_addr_t ptr, ptr_end;char *buffer;char *save_argv[6 ];
@@ -390,13 +447,10 @@ static int pstore_save(struct cmd_tbl *cmdtp, int flag, int argc,return CMD_RET_USAGE ; } -if (buffer_size ==0 ) -pstore_init_buffer_size (); - - buffer =malloc_cache_aligned (buffer_size); + size =pstore_get_zones (&zones); + buffer =malloc_cache_aligned (size);if (!buffer) { -printf ("Cannot allocate %u bytes for the records\n" , - buffer_size); +printf ("Cannot allocate %u bytes for the records\n" , size);return CMD_RET_FAILURE ; }sprintf (addr,"0x%p" , buffer);
@@ -409,14 +463,13 @@ static int pstore_save(struct cmd_tbl *cmdtp, int flag, int argc, save_argv[5 ] = length;/* Save all Dump records */ - ptr = pstore_addr; - ptr_end = ptr + pstore_length - pstore_pmsg_size - pstore_ftrace_size - - pstore_console_size; + ptr = zones.dump.addr; + ptr_end = zones.dump_end; index =0 ;while (ptr < ptr_end) { size =pstore_get_buffer (PERSISTENT_RAM_SIG , ptr, - pstore_record_size, buffer); - ptr += pstore_record_size; + zones.dump.size, buffer); + ptr += zones.dump.size;if (size ==0 )continue ;
@@ -438,30 +491,29 @@ static int pstore_save(struct cmd_tbl *cmdtp, int flag, int argc,sprintf (addr,"0x%08lx" , (ulong )map_to_sysmem (buffer));/* Save Console record */ - size =pstore_get_buffer (PERSISTENT_RAM_SIG , ptr, pstore_console_size, - buffer); + size =pstore_get_buffer (PERSISTENT_RAM_SIG , zones.console.addr, + zones.console.size, buffer);if (size !=0 ) {sprintf (length,"0x%X" , size);snprintf (path,sizeof (path),"%s/console-ramoops-0" , argv[3 ]);do_save (cmdtp, flag,6 , save_argv,FS_TYPE_ANY ); } - ptr += pstore_console_size;/* Save FTrace record(s) * The FTrace record(s) uses LINUX_VERSION_CODE ^ 'DBGC' signature, * pass 0 to pstore_get_buffer to prevent checking it */ - size =pstore_get_buffer (0 , ptr, pstore_ftrace_size, buffer); + size =pstore_get_buffer (0 , zones.ftrace.addr, zones.ftrace.size, + buffer);if (size !=0 ) {sprintf (length,"0x%X" , size);snprintf (path,sizeof (path),"%s/ftrace-ramoops-0" , argv[3 ]);do_save (cmdtp, flag,6 , save_argv,FS_TYPE_ANY ); } - ptr += pstore_ftrace_size;/* Save Console record */ - size =pstore_get_buffer (PERSISTENT_RAM_SIG , ptr, pstore_pmsg_size, - buffer); + size =pstore_get_buffer (PERSISTENT_RAM_SIG , zones.pmsg.addr, + zones.pmsg.size, buffer);if (size !=0 ) {sprintf (length,"0x%X" , size);snprintf (path,sizeof (path),"%s/pmsg-ramoops-0" , argv[3 ]);
Signature · 1 line
2.43.0
PATCH 4/6 test: pstore: check that the zones are found where Linux puts them
In reply to Aristo ChenSet sizes that Linux rounds down, with memory left over for the dump records, then write a dump record larger than the record size in the second dump zone and a console record where Linux puts them. Check that pstore display shows both records, that pstore display dump shows nothing for a dump zone that does not exist, also when the offset of its index wraps around in 64 bits, and that pstore save writes both records.
Also check that the console zone is found at the start of the memory when the record size is 0, and that pstore set accepts sizes that only fit once rounded down and rejects sizes whose sum wraps around in 32 bits.
Without the zone layout fix, the first test finds neither record, the last one fails on pstore set, and in the second one pstore display loops forever, which test.py treats as a lab failure that stops the whole run.
--- test/py/tests/test_pstore.py | 98 ++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+)
test/py/tests/test_pstore.py +98 -0
@@ -197,3 +197,101 @@ def test_pstore_buffer_too_large(ubman): finally: # Restore the default record sizes ubman.restart_uboot() + [email protected]('cmd_pstore') +def test_pstore_zone_layout(ubman): + """Test that pstore finds the zones where Linux puts them.""" + + # Linux rounds the console size down to 0x1000, which leaves 0xf000 for + # the dump records: 3 zones of 0x5000 for a record size of 0x4000 + dump1 = PSTORE_ADDR + 0x5000 + console = PSTORE_ADDR + 0xf000 + # Larger than the record size, but fits in the zone + dump = b'x' * 0x4800 + b'\nend of dump 1\n' + # Looks like a dump record, to catch a read past the dump zones + text = b'====1.0-D\nconsole text\n' + + ubman.run_command('') + try: + ubman.run_command_list([ + 'mw.b 0x%x 0 0x%x' % (PSTORE_ADDR, PSTORE_LENGTH), + 'pstore set 0x%x 0x12000 0x4000 0x1800 0x1000 0x1000' % + (PSTORE_ADDR)]) + write_record(ubman, dump1, PSTORE_SIG, b'====1.0-D\n' + dump) + write_record(ubman, console, PSTORE_SIG, text) + + response = ubman.run_command('pstore display') + assert('end of dump 1' in response) + assert('console text' in response) + + # There is no dump zone 3 + response = ubman.run_command('pstore display dump 3') + assert('****' not in response) + if ubman.config.buildconfig.get('config_64bit') == 'y': + # Nor zone 2^52 + 1, whose offset wraps around to zone 1 + response = ubman.run_command('pstore display dump %d' % + ((1 << 52) + 1)) + assert('****' not in response) + + with tempfile.TemporaryDirectory() as outdir: + ubman.run_command('pstore save hostfs - %s' % (outdir)) + with open('%s/dmesg-ramoops-0' % (outdir), 'rb') as f: + assert(f.read() == dump) + with open('%s/console-ramoops-0' % (outdir), 'rb') as f: + assert(f.read() == text) + finally: + # Restore the default record sizes + ubman.restart_uboot() + [email protected]('cmd_pstore') +def test_pstore_no_dump_zones(ubman): + """Test that pstore finds the console zone with a record size of 0.""" + + ubman.run_command('') + try: + ubman.run_command_list([ + 'mw.b 0x%x 0 0x%x' % (PSTORE_ADDR, PSTORE_LENGTH), + 'pstore set 0x%x 0x%x 0 0x1000 0x1000 0x1000' % + (PSTORE_ADDR, PSTORE_LENGTH)]) + # Linux makes no dump zones, the console zone comes first + write_record(ubman, PSTORE_ADDR, PSTORE_SIG, b'console text\n') + + response = ubman.run_command('pstore display') + assert('console text' in response) + response = ubman.run_command('pstore display dump 0') + assert('****' not in response) + + with tempfile.TemporaryDirectory() as outdir: + ubman.run_command('pstore save hostfs - %s' % (outdir)) + with open('%s/console-ramoops-0' % (outdir), 'rb') as f: + assert(f.read() == b'console text\n') + finally: + # Restore the default record sizes + ubman.restart_uboot() + [email protected]('cmd_pstore') +def test_pstore_rounded_sizes(ubman): + """Test that pstore set checks the sizes as Linux rounds them.""" + + ubman.run_command('') + try: + # Rounded down to 0x1000, a dump and a console zone fit in 0x2000 + ubman.run_command('mw.b 0x%x 0 0x2000' % (PSTORE_ADDR)) + response = ubman.run_command('pstore set 0x%x 0x2000 0x1800 0x1800 0 0' + % (PSTORE_ADDR)) + assert('should be larger' not in response) + write_record(ubman, PSTORE_ADDR + 0x1000, PSTORE_SIG, + b'console text\n') + response = ubman.run_command('pstore display console') + assert('console text' in response) + + # Sizes whose sum wraps around in 32 bits + response = ubman.run_command( + 'pstore set 0x%x 0x%x 0x80000000 0x80000000 0x80000000 0x80000000' + % (PSTORE_ADDR, PSTORE_LENGTH)) + assert('should be larger' in response) + response = ubman.run_command('pstore display') + assert('Please set PStore configuration' in response) + finally: + # Restore the default record sizes + ubman.restart_uboot()
Signature · 1 line
2.43.0
PATCH 5/6 cmd: pstore: find the user space zone where Linux 6.2 puts it
In reply to Aristo Chenpstore display and pstore save expect the zones in the order dump, console, ftrace, user space (pmsg), which Linux used up to 6.1. Since commit 6daf4e82bd54 ("pstore/ram: Move pmsg init earlier") in 6.2, Linux puts the user space zone before the ftrace zone. With such a kernel, U-Boot shows and saves the user space record as the ftrace one and does not find the ftrace record, and when the two zones differ in size, it reads both at the wrong place.
Use the new order, but keep the old one for older kernels. Linux writes the signature of each zone when it starts: plain 'DBGC' for the user space zone and 'DBGC' XORed with LINUX_VERSION_CODE for the ftrace zone. So take the old order when the zone after the console zone has an ftrace signature, that is, 'DBGC' XORed with a plausible kernel version. The order only differs when both zones are used, and a zone too small for a record is left alone.
Older kernels with per-CPU ftrace zones (RAMOOPS_FLAG_FTRACE_PER_CPU, which the device tree fixup of U-Boot does not set) can leave a few bytes unused at the end of the ftrace memory, which moves the user space zone. As before, that case is not handled.
--- cmd/pstore.c | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-)
cmd/pstore.c +32 -3
@@ -162,6 +162,26 @@ static u32 pstore_round_size(u32 size)return size ?rounddown_pow_of_two (size) :0 ; } +/** +* pstore_has_ftrace_sig() - Check whether a zone has an FTrace signature +* @addr: Address of the zone +* +* Linux writes the signature of each zone when it starts: 'DBGC' XORed with +* LINUX_VERSION_CODE in the FTrace zones, the plain 'DBGC' in the others. +* +* Return: true if the zone at @addr has the signature of an FTrace zone +*/ +static bool pstore_has_ftrace_sig (phys_addr_t addr) +{ +u32 *sig =map_sysmem (addr,sizeof (*sig)); +u32 version = *sig ^PERSISTENT_RAM_SIG ; + +unmap_sysmem (sig); + +/* LINUX_VERSION_CODE of 3.0 to 255.255.255 */ +return version >=0x30000 && version <=0xffffff ; +} +/** * pstore_get_zones() - Find the zones where Linux keeps the records * @zones: Returns the zones
@@ -169,7 +189,8 @@ static u32 pstore_round_size(u32 size)* Lay out the zones like ramoops_probe() in Linux: round each size down to a * power of two, split the memory left for the dump records into as many * zones as there is room for records, each rounded down to an even size, then -* put the console, FTrace and user space zones after them. +* put the console, user space and FTrace zones after them. Linux 6.1 and older +* put the FTrace zone before the user space one, which its signature shows. * * Return: size of the largest zone, which a buffer needs for any record */
@@ -193,8 +214,16 @@ static u32 pstore_get_zones(struct pstore_zones *zones) (phys_size_t )count * zones->dump.size; zones->console.addr = zones->dump_end; - zones->ftrace.addr = zones->console.addr + zones->console.size; - zones->pmsg.addr = zones->ftrace.addr + zones->ftrace.size; + zones->pmsg.addr = zones->console.addr + zones->console.size; + zones->ftrace.addr = zones->pmsg.addr + zones->pmsg.size; + +/* A zone too small for a record holds none, so its place is moot */ +if (zones->pmsg.size >sizeof (struct persistent_ram_buffer ) && + zones->ftrace.size >sizeof (struct persistent_ram_buffer ) && +pstore_has_ftrace_sig (zones->pmsg.addr)) { + zones->ftrace.addr = zones->pmsg.addr; + zones->pmsg.addr += zones->ftrace.size; + }return max (max (zones->dump.size, zones->console.size),max (zones->ftrace.size, zones->pmsg.size));
Signature · 1 line
2.43.0
PATCH 6/6 test: pstore: check the ftrace and user zones in both orders
In reply to Aristo ChenUse an ftrace zone larger than the user space one, write a user space record and an ftrace record in the order of Linux 6.2 and later, and check that pstore display and pstore save find each record in its own zone. Then do the same in the order of older kernels. Without the order fix, the user space record of the first case is shown as the ftrace record and no user space record is found.
Also write a user space record without a signature in the new order, and an ftrace record whose data looks like a user space record where the old order puts the user space zone, and check that this data is not taken for a user space record.
test_pstore_invalid_records wrote its user space and ftrace records in the old order; swap them, so that the record with an invalid start is still read as an ftrace record, without checking its signature.
--- test/py/tests/test_pstore.py | 64 ++++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 2 deletions(-)
test/py/tests/test_pstore.py +62 -2
@@ -99,8 +99,8 @@ def test_pstore_invalid_records(ubman): """Test that pstore skips records with an invalid header.""" console = PSTORE_ADDR + PSTORE_LENGTH - 3 * 4096 - ftrace = console + 4096 - pmsg = ftrace + 4096 + pmsg = console + 4096 + ftrace = pmsg + 4096 outdir = tempfile.mkdtemp() ubman.run_command('')
@@ -295,3 +295,63 @@ def test_pstore_rounded_sizes(ubman): finally: # Restore the default record sizes ubman.restart_uboot() + [email protected]('cmd_pstore') +def test_pstore_zone_order(ubman): + """Test that pstore finds the ftrace and user zones in both orders.""" + + # The ftrace zone is larger than the user space one, so that the two + # zones do not just swap places + console = PSTORE_ADDR + PSTORE_LENGTH - 0x4000 + orders = [ + # Linux 6.2 and later put the user space zone first + (console + 0x1000, console + 0x2000, 0x060200), + # Older kernels put the ftrace zone first + (console + 0x3000, console + 0x1000, 0x060100), + ] + + ubman.run_command('') + try: + ubman.run_command('pstore set 0x%x 0x%x 0x1000 0x1000 0x2000 0x1000' % + (PSTORE_ADDR, PSTORE_LENGTH)) + for pmsg, ftrace, version in orders: + ubman.run_command('mw.b 0x%x 0 0x%x' % (PSTORE_ADDR, + PSTORE_LENGTH)) + write_record(ubman, pmsg, PSTORE_SIG, b'user text\n') + # The ftrace signature holds the Linux version + write_record(ubman, ftrace, PSTORE_SIG ^ version, + b'ftrace text\n') + + response = ubman.run_command('pstore display user') + assert('user text' in response) + assert('ftrace text' not in response) + response = ubman.run_command('pstore display ftrace') + assert('ftrace text' in response) + assert('user text' not in response) + + with tempfile.TemporaryDirectory() as outdir: + ubman.run_command('pstore save hostfs - %s' % (outdir)) + with open('%s/pmsg-ramoops-0' % (outdir), 'rb') as f: + assert(f.read() == b'user text\n') + with open('%s/ftrace-ramoops-0' % (outdir), 'rb') as f: + assert(f.read() == b'ftrace text\n') + + # A user space record without a signature in the new order, and an + # ftrace record with data that looks like a user space record where + # the old order puts the user space zone + ubman.run_command('mw.b 0x%x 0 0x%x' % (PSTORE_ADDR, PSTORE_LENGTH)) + write_record(ubman, console + 0x1000, 0, b'user text\n') + data = (b'ftrace text\n' + b'x' * (0x1000 - 12 - 12) + + struct.pack('<III', PSTORE_SIG, 0, 6) + b'bogus\n') + write_record(ubman, console + 0x2000, PSTORE_SIG ^ 0x060200, data) + + response = ubman.run_command('pstore display user') + assert('****' not in response) + with tempfile.TemporaryDirectory() as outdir: + ubman.run_command('pstore save hostfs - %s' % (outdir)) + assert(not os.path.exists('%s/pmsg-ramoops-0' % (outdir))) + with open('%s/ftrace-ramoops-0' % (outdir), 'rb') as f: + assert(f.read() == data) + finally: + # Restore the default record sizes + ubman.restart_uboot()
Signature · 1 line
2.43.0