From 791205e3ec6081a8da6f00621e3453d622dc41e7 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Wed, 13 May 2020 14:35:03 -0700 Subject: pstore/ram: Introduce max_reason and convert dump_oops Now that pstore_register() can correctly pass max_reason to the kmesg dump facility, introduce a new "max_reason" module parameter and "max-reason" Device Tree field. The "dump_oops" module parameter and "dump-oops" Device Tree field are now considered deprecated, but are now automatically converted to their corresponding max_reason values when present, though the new max_reason setting has precedence. For struct ramoops_platform_data, the "dump_oops" member is entirely replaced by a new "max_reason" member, with the only existing user updated in place. Additionally remove the "reason" filter logic from ramoops_pstore_write(), as that is not specifically needed anymore, though technically this is a change in behavior for any ramoops users also setting the printk.always_kmsg_dump boot param, which will cause ramoops to behave as if max_reason was set to KMSG_DUMP_MAX. Co-developed-by: Pavel Tatashin Signed-off-by: Pavel Tatashin Link: https://lore.kernel.org/lkml/20200515184434.8470-6-keescook@chromium.org/ Signed-off-by: Kees Cook --- Documentation/admin-guide/ramoops.rst | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'Documentation/admin-guide') diff --git a/Documentation/admin-guide/ramoops.rst b/Documentation/admin-guide/ramoops.rst index 6dbcc5481000..a60a96218ba9 100644 --- a/Documentation/admin-guide/ramoops.rst +++ b/Documentation/admin-guide/ramoops.rst @@ -32,11 +32,17 @@ memory to be mapped strongly ordered, and atomic operations on strongly ordered memory are implementation defined, and won't work on many ARMs such as omaps. The memory area is divided into ``record_size`` chunks (also rounded down to -power of two) and each oops/panic writes a ``record_size`` chunk of +power of two) and each kmesg dump writes a ``record_size`` chunk of information. -Dumping both oopses and panics can be done by setting 1 in the ``dump_oops`` -variable while setting 0 in that variable dumps only the panics. +Limiting which kinds of kmsg dumps are stored can be controlled via +the ``max_reason`` value, as defined in include/linux/kmsg_dump.h's +``enum kmsg_dump_reason``. For example, to store both Oopses and Panics, +``max_reason`` should be set to 2 (KMSG_DUMP_OOPS), to store only Panics +``max_reason`` should be set to 1 (KMSG_DUMP_PANIC). Setting this to 0 +(KMSG_DUMP_UNDEF), means the reason filtering will be controlled by the +``printk.always_kmsg_dump`` boot param: if unset, it'll be KMSG_DUMP_OOPS, +otherwise KMSG_DUMP_MAX. The module uses a counter to record multiple dumps but the counter gets reset on restart (i.e. new dumps after the restart will overwrite old ones). @@ -90,7 +96,7 @@ Setting the ramoops parameters can be done in several different manners: .mem_address = <...>, .mem_type = <...>, .record_size = <...>, - .dump_oops = <...>, + .max_reason = <...>, .ecc = <...>, }; -- cgit v1.2.3 From 649304c936cd4d2a2128bb877044772416c7d4f5 Mon Sep 17 00:00:00 2001 From: WeiXiong Liao Date: Wed, 25 Mar 2020 16:55:02 +0800 Subject: Documentation: Add details for pstore/blk Add details on using pstore/blk, the new backend of pstore to record dumps to block devices, in Documentation/admin-guide/pstore-blk.rst Signed-off-by: WeiXiong Liao Link: https://lore.kernel.org/lkml/20200511233229.27745-7-keescook@chromium.org/ Signed-off-by: Kees Cook --- Documentation/admin-guide/pstore-blk.rst | 229 +++++++++++++++++++++++++++++++ MAINTAINERS | 1 + fs/pstore/Kconfig | 2 + 3 files changed, 232 insertions(+) create mode 100644 Documentation/admin-guide/pstore-blk.rst (limited to 'Documentation/admin-guide') diff --git a/Documentation/admin-guide/pstore-blk.rst b/Documentation/admin-guide/pstore-blk.rst new file mode 100644 index 000000000000..bef8c7436721 --- /dev/null +++ b/Documentation/admin-guide/pstore-blk.rst @@ -0,0 +1,229 @@ +.. SPDX-License-Identifier: GPL-2.0 + +pstore block oops/panic logger +============================== + +Introduction +------------ + +pstore block (pstore/blk) is an oops/panic logger that writes its logs to a +block device before the system crashes. You can get these log files by +mounting pstore filesystem like:: + + mount -t pstore pstore /sys/fs/pstore + + +pstore block concepts +--------------------- + +pstore/blk provides efficient configuration method for pstore/blk, which +divides all configurations into two parts, configurations for user and +configurations for driver. + +Configurations for user determine how pstore/blk works, such as pmsg_size, +kmsg_size and so on. All of them support both Kconfig and module parameters, +but module parameters have priority over Kconfig. + +Configurations for driver are all about block device, such as total_size +of block device and read/write operations. + +Configurations for user +----------------------- + +All of these configurations support both Kconfig and module parameters, but +module parameters have priority over Kconfig. + +Here is an example for module parameters:: + + pstore_blk.blkdev=179:7 pstore_blk.kmsg_size=64 + +The detail of each configurations may be of interest to you. + +blkdev +~~~~~~ + +The block device to use. Most of the time, it is a partition of block device. +It's required for pstore/blk. + +It accepts the following variants: + +1. device number in hexadecimal represents itself; no + leading 0x, for example b302. +#. /dev/ represents the device number of disk +#. /dev/ represents the device number of partition - device + number of disk plus the partition number +#. /dev/p - same as the above; this form is used when disk + name of partitioned disk ends with a digit. +#. PARTUUID=00112233-4455-6677-8899-AABBCCDDEEFF represents the unique id of + a partition if the partition table provides it. The UUID may be either an + EFI/GPT UUID, or refer to an MSDOS partition using the format SSSSSSSS-PP, + where SSSSSSSS is a zero-filled hex representation of the 32-bit + "NT disk signature", and PP is a zero-filled hex representation of the + 1-based partition number. +#. PARTUUID=/PARTNROFF= to select a partition in relation to a + partition with a known unique id. +#. : major and minor number of the device separated by a colon. + +kmsg_size +~~~~~~~~~ + +The chunk size in KB for oops/panic front-end. It **MUST** be a multiple of 4. +It's optional if you do not care oops/panic log. + +There are multiple chunks for oops/panic front-end depending on the remaining +space except other pstore front-ends. + +pstore/blk will log to oops/panic chunks one by one, and always overwrite the +oldest chunk if there is no more free chunk. + +pmsg_size +~~~~~~~~~ + +The chunk size in KB for pmsg front-end. It **MUST** be a multiple of 4. +It's optional if you do not care pmsg log. + +Unlike oops/panic front-end, there is only one chunk for pmsg front-end. + +Pmsg is a user space accessible pstore object. Writes to */dev/pmsg0* are +appended to the chunk. On reboot the contents are available in +*/sys/fs/pstore/pmsg-pstore-blk-0*. + +console_size +~~~~~~~~~~~~ + +The chunk size in KB for console front-end. It **MUST** be a multiple of 4. +It's optional if you do not care console log. + +Similar to pmsg front-end, there is only one chunk for console front-end. + +All log of console will be appended to the chunk. On reboot the contents are +available in */sys/fs/pstore/console-pstore-blk-0*. + +ftrace_size +~~~~~~~~~~~ + +The chunk size in KB for ftrace front-end. It **MUST** be a multiple of 4. +It's optional if you do not care console log. + +Similar to oops front-end, there are multiple chunks for ftrace front-end +depending on the count of cpu processors. Each chunk size is equal to +ftrace_size / processors_count. + +All log of ftrace will be appended to the chunk. On reboot the contents are +combined and available in */sys/fs/pstore/ftrace-pstore-blk-0*. + +Persistent function tracing might be useful for debugging software or hardware +related hangs. Here is an example of usage:: + + # mount -t pstore pstore /sys/fs/pstore + # mount -t debugfs debugfs /sys/kernel/debug/ + # echo 1 > /sys/kernel/debug/pstore/record_ftrace + # reboot -f + [...] + # mount -t pstore pstore /sys/fs/pstore + # tail /sys/fs/pstore/ftrace-pstore-blk-0 + CPU:0 ts:5914676 c0063828 c0063b94 call_cpuidle <- cpu_startup_entry+0x1b8/0x1e0 + CPU:0 ts:5914678 c039ecdc c006385c cpuidle_enter_state <- call_cpuidle+0x44/0x48 + CPU:0 ts:5914680 c039e9a0 c039ecf0 cpuidle_enter_freeze <- cpuidle_enter_state+0x304/0x314 + CPU:0 ts:5914681 c0063870 c039ea30 sched_idle_set_state <- cpuidle_enter_state+0x44/0x314 + CPU:1 ts:5916720 c0160f59 c015ee04 kernfs_unmap_bin_file <- __kernfs_remove+0x140/0x204 + CPU:1 ts:5916721 c05ca625 c015ee0c __mutex_lock_slowpath <- __kernfs_remove+0x148/0x204 + CPU:1 ts:5916723 c05c813d c05ca630 yield_to <- __mutex_lock_slowpath+0x314/0x358 + CPU:1 ts:5916724 c05ca2d1 c05ca638 __ww_mutex_lock <- __mutex_lock_slowpath+0x31c/0x358 + +max_reason +~~~~~~~~~~ + +Limiting which kinds of kmsg dumps are stored can be controlled via +the ``max_reason`` value, as defined in include/linux/kmsg_dump.h's +``enum kmsg_dump_reason``. For example, to store both Oopses and Panics, +``max_reason`` should be set to 2 (KMSG_DUMP_OOPS), to store only Panics +``max_reason`` should be set to 1 (KMSG_DUMP_PANIC). Setting this to 0 +(KMSG_DUMP_UNDEF), means the reason filtering will be controlled by the +``printk.always_kmsg_dump`` boot param: if unset, it'll be KMSG_DUMP_OOPS, +otherwise KMSG_DUMP_MAX. + +Configurations for driver +------------------------- + +Only a block device driver cares about these configurations. A block device +driver uses ``register_pstore_blk`` to register to pstore/blk. + +.. kernel-doc:: fs/pstore/blk.c + :identifiers: register_pstore_blk + +Compression and header +---------------------- + +Block device is large enough for uncompressed oops data. Actually we do not +recommend data compression because pstore/blk will insert some information into +the first line of oops/panic data. For example:: + + Panic: Total 16 times + +It means that it's OOPS|Panic for the 16th time since the first booting. +Sometimes the number of occurrences of oops|panic since the first booting is +important to judge whether the system is stable. + +The following line is inserted by pstore filesystem. For example:: + + Oops#2 Part1 + +It means that it's OOPS for the 2nd time on the last boot. + +Reading the data +---------------- + +The dump data can be read from the pstore filesystem. The format for these +files is ``dmesg-pstore-blk-[N]`` for oops/panic front-end, +``pmsg-pstore-blk-0`` for pmsg front-end and so on. The timestamp of the +dump file records the trigger time. To delete a stored record from block +device, simply unlink the respective pstore file. + +Attentions in panic read/write APIs +----------------------------------- + +If on panic, the kernel is not going to run for much longer, the tasks will not +be scheduled and most kernel resources will be out of service. It +looks like a single-threaded program running on a single-core computer. + +The following points require special attention for panic read/write APIs: + +1. Can **NOT** allocate any memory. + If you need memory, just allocate while the block driver is initializing + rather than waiting until the panic. +#. Must be polled, **NOT** interrupt driven. + No task schedule any more. The block driver should delay to ensure the write + succeeds, but NOT sleep. +#. Can **NOT** take any lock. + There is no other task, nor any shared resource; you are safe to break all + locks. +#. Just use CPU to transfer. + Do not use DMA to transfer unless you are sure that DMA will not keep lock. +#. Control registers directly. + Please control registers directly rather than use Linux kernel resources. + Do I/O map while initializing rather than wait until a panic occurs. +#. Reset your block device and controller if necessary. + If you are not sure of the state of your block device and controller when + a panic occurs, you are safe to stop and reset them. + +pstore/blk supports psblk_blkdev_info(), which is defined in +*linux/pstore_blk.h*, to get information of using block device, such as the +device number, sector count and start sector of the whole disk. + +pstore block internals +---------------------- + +For developer reference, here are all the important structures and APIs: + +.. kernel-doc:: fs/pstore/zone.c + :internal: + +.. kernel-doc:: include/linux/pstore_zone.h + :internal: + +.. kernel-doc:: fs/pstore/blk.c + :export: + +.. kernel-doc:: include/linux/pstore_blk.h + :internal: diff --git a/MAINTAINERS b/MAINTAINERS index e64e5db31497..9c1f4feff418 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13660,6 +13660,7 @@ M: Tony Luck S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git for-next/pstore F: Documentation/admin-guide/ramoops.rst +F: Documentation/admin-guide/pstore-blk.rst F: Documentation/devicetree/bindings/reserved-memory/ramoops.txt F: drivers/acpi/apei/erst.c F: drivers/firmware/efi/efi-pstore.c diff --git a/fs/pstore/Kconfig b/fs/pstore/Kconfig index c2237984b407..e16a49ebfe54 100644 --- a/fs/pstore/Kconfig +++ b/fs/pstore/Kconfig @@ -171,6 +171,8 @@ config PSTORE_BLK This enables panic and oops message to be logged to a block dev where it can be read back at some later point. + For more information, see Documentation/admin-guide/pstore-blk.rst + If unsure, say N. config PSTORE_BLK_BLKDEV -- cgit v1.2.3 From 7dcb7848ba110ff192efc917d1a6de66b4c9ca4f Mon Sep 17 00:00:00 2001 From: WeiXiong Liao Date: Wed, 25 Mar 2020 16:55:05 +0800 Subject: pstore/blk: Support non-block storage devices Add support for non-block devices (e.g. MTD). A non-block driver calls pstore_blk_register_device() to register iself. In addition, pstore/zone is updated to handle non-block devices, where an erase must be done before a write. Without this, there is no way to remove records stored to an MTD. Signed-off-by: WeiXiong Liao Link: https://lore.kernel.org/lkml/20200511233229.27745-10-keescook@chromium.org/ Co-developed-by: Kees Cook Signed-off-by: Kees Cook --- Documentation/admin-guide/pstore-blk.rst | 17 ++++-- fs/pstore/blk.c | 93 +++++++++++++++++--------------- fs/pstore/zone.c | 8 ++- include/linux/pstore_blk.h | 38 +++++++++++++ include/linux/pstore_zone.h | 6 +++ 5 files changed, 114 insertions(+), 48 deletions(-) (limited to 'Documentation/admin-guide') diff --git a/Documentation/admin-guide/pstore-blk.rst b/Documentation/admin-guide/pstore-blk.rst index bef8c7436721..d45341e55e82 100644 --- a/Documentation/admin-guide/pstore-blk.rst +++ b/Documentation/admin-guide/pstore-blk.rst @@ -7,8 +7,8 @@ Introduction ------------ pstore block (pstore/blk) is an oops/panic logger that writes its logs to a -block device before the system crashes. You can get these log files by -mounting pstore filesystem like:: +block device and non-block device before the system crashes. You can get +these log files by mounting pstore filesystem like:: mount -t pstore pstore /sys/fs/pstore @@ -24,8 +24,8 @@ Configurations for user determine how pstore/blk works, such as pmsg_size, kmsg_size and so on. All of them support both Kconfig and module parameters, but module parameters have priority over Kconfig. -Configurations for driver are all about block device, such as total_size -of block device and read/write operations. +Configurations for driver are all about block device and non-block device, +such as total_size of block device and read/write operations. Configurations for user ----------------------- @@ -152,6 +152,15 @@ driver uses ``register_pstore_blk`` to register to pstore/blk. .. kernel-doc:: fs/pstore/blk.c :identifiers: register_pstore_blk +A non-block device driver uses ``register_pstore_device`` with +``struct pstore_device_info`` to register to pstore/blk. + +.. kernel-doc:: fs/pstore/blk.c + :identifiers: register_pstore_device + +.. kernel-doc:: include/linux/pstore_blk.h + :identifiers: pstore_device_info + Compression and header ---------------------- diff --git a/fs/pstore/blk.c b/fs/pstore/blk.c index 631bc27c8661..881b40ed8142 100644 --- a/fs/pstore/blk.c +++ b/fs/pstore/blk.c @@ -105,55 +105,22 @@ struct bdev_info { _##name_; \ }) -/** - * struct pstore_device_info - back-end pstore/blk driver structure. - * - * @total_size: The total size in bytes pstore/blk can use. It must be greater - * than 4096 and be multiple of 4096. - * @flags: Refer to macro starting with PSTORE_FLAGS defined in - * linux/pstore.h. It means what front-ends this device support. - * Zero means all backends for compatible. - * @read: The general read operation. Both of the function parameters - * @size and @offset are relative value to bock device (not the - * whole disk). - * On success, the number of bytes should be returned, others - * means error. - * @write: The same as @read, but the following error number: - * -EBUSY means try to write again later. - * -ENOMSG means to try next zone. - * @panic_write:The write operation only used for panic case. It's optional - * if you do not care panic log. The parameters are relative - * value to storage. - * On success, the number of bytes should be returned, others - * excluding -ENOMSG mean error. -ENOMSG means to try next zone. - */ -struct pstore_device_info { - unsigned long total_size; - unsigned int flags; - pstore_zone_read_op read; - pstore_zone_write_op write; - pstore_zone_write_op panic_write; -}; - -static int psblk_register_do(struct pstore_device_info *dev) +static int __register_pstore_device(struct pstore_device_info *dev) { int ret; + lockdep_assert_held(&pstore_blk_lock); + if (!dev || !dev->total_size || !dev->read || !dev->write) return -EINVAL; - mutex_lock(&pstore_blk_lock); - /* someone already registered before */ - if (pstore_zone_info) { - mutex_unlock(&pstore_blk_lock); + if (pstore_zone_info) return -EBUSY; - } + pstore_zone_info = kzalloc(sizeof(struct pstore_zone_info), GFP_KERNEL); - if (!pstore_zone_info) { - mutex_unlock(&pstore_blk_lock); + if (!pstore_zone_info) return -ENOMEM; - } /* zero means not limit on which backends to attempt to store. */ if (!dev->flags) @@ -179,6 +146,7 @@ static int psblk_register_do(struct pstore_device_info *dev) pstore_zone_info->max_reason = max_reason; pstore_zone_info->read = dev->read; pstore_zone_info->write = dev->write; + pstore_zone_info->erase = dev->erase; pstore_zone_info->panic_write = dev->panic_write; pstore_zone_info->name = KBUILD_MODNAME; pstore_zone_info->owner = THIS_MODULE; @@ -188,20 +156,51 @@ static int psblk_register_do(struct pstore_device_info *dev) kfree(pstore_zone_info); pstore_zone_info = NULL; } + return ret; +} +/** + * register_pstore_device() - register non-block device to pstore/blk + * + * @dev: non-block device information + * + * Return: + * * 0 - OK + * * Others - something error. + */ +int register_pstore_device(struct pstore_device_info *dev) +{ + int ret; + + mutex_lock(&pstore_blk_lock); + ret = __register_pstore_device(dev); mutex_unlock(&pstore_blk_lock); + return ret; } +EXPORT_SYMBOL_GPL(register_pstore_device); -static void psblk_unregister_do(struct pstore_device_info *dev) +static void __unregister_pstore_device(struct pstore_device_info *dev) { - mutex_lock(&pstore_blk_lock); + lockdep_assert_held(&pstore_blk_lock); if (pstore_zone_info && pstore_zone_info->read == dev->read) { unregister_pstore_zone(pstore_zone_info); kfree(pstore_zone_info); pstore_zone_info = NULL; } +} + +/** + * unregister_pstore_device() - unregister non-block device from pstore/blk + * + * @dev: non-block device information + */ +void unregister_pstore_device(struct pstore_device_info *dev) +{ + mutex_lock(&pstore_blk_lock); + __unregister_pstore_device(dev); mutex_unlock(&pstore_blk_lock); } +EXPORT_SYMBOL_GPL(unregister_pstore_device); /** * psblk_get_bdev() - open block device @@ -396,9 +395,10 @@ static int __register_pstore_blk(struct pstore_blk_info *info) dev.flags = info->flags; dev.read = psblk_generic_blk_read; dev.write = psblk_generic_blk_write; + dev.erase = NULL; dev.panic_write = info->panic_write ? psblk_blk_panic_write : NULL; - ret = psblk_register_do(&dev); + ret = __register_pstore_device(&dev); if (ret) goto err_put_bdev; @@ -442,7 +442,7 @@ static void __unregister_pstore_blk(unsigned int major) lockdep_assert_held(&pstore_blk_lock); if (psblk_bdev && MAJOR(psblk_bdev->bd_dev) == major) { - psblk_unregister_do(&dev); + __unregister_pstore_device(&dev); psblk_put_bdev(psblk_bdev, holder); blkdev_panic_write = NULL; psblk_bdev = NULL; @@ -481,6 +481,13 @@ static void __exit pstore_blk_exit(void) mutex_lock(&pstore_blk_lock); if (psblk_bdev) __unregister_pstore_blk(MAJOR(psblk_bdev->bd_dev)); + else { + struct pstore_device_info dev = { }; + + if (pstore_zone_info) + dev.read = pstore_zone_info->read; + __unregister_pstore_device(&dev); + } mutex_unlock(&pstore_blk_lock); } module_exit(pstore_blk_exit); diff --git a/fs/pstore/zone.c b/fs/pstore/zone.c index add26b125984..819428dfa32f 100644 --- a/fs/pstore/zone.c +++ b/fs/pstore/zone.c @@ -660,15 +660,21 @@ static inline int psz_kmsg_erase(struct psz_context *cxt, struct psz_buffer *buffer = zone->buffer; struct psz_kmsg_header *hdr = (struct psz_kmsg_header *)buffer->data; + size_t size; if (unlikely(!psz_ok(zone))) return 0; + /* this zone is already updated, no need to erase */ if (record->count != hdr->counter) return 0; + size = buffer_datalen(zone) + sizeof(*zone->buffer); atomic_set(&zone->buffer->datalen, 0); - return psz_zone_write(zone, FLUSH_META, NULL, 0, 0); + if (cxt->pstore_zone_info->erase) + return cxt->pstore_zone_info->erase(size, zone->off); + else + return psz_zone_write(zone, FLUSH_META, NULL, 0, 0); } static inline int psz_record_erase(struct psz_context *cxt, diff --git a/include/linux/pstore_blk.h b/include/linux/pstore_blk.h index 0c40774e71e0..61e914522b01 100644 --- a/include/linux/pstore_blk.h +++ b/include/linux/pstore_blk.h @@ -49,6 +49,44 @@ struct pstore_blk_info { int register_pstore_blk(struct pstore_blk_info *info); void unregister_pstore_blk(unsigned int major); +/** + * struct pstore_device_info - back-end pstore/blk driver structure. + * + * @total_size: The total size in bytes pstore/blk can use. It must be greater + * than 4096 and be multiple of 4096. + * @flags: Refer to macro starting with PSTORE_FLAGS defined in + * linux/pstore.h. It means what front-ends this device support. + * Zero means all backends for compatible. + * @read: The general read operation. Both of the function parameters + * @size and @offset are relative value to bock device (not the + * whole disk). + * On success, the number of bytes should be returned, others + * means error. + * @write: The same as @read, but the following error number: + * -EBUSY means try to write again later. + * -ENOMSG means to try next zone. + * @erase: The general erase operation for device with special removing + * job. Both of the function parameters @size and @offset are + * relative value to storage. + * Return 0 on success and others on failure. + * @panic_write:The write operation only used for panic case. It's optional + * if you do not care panic log. The parameters are relative + * value to storage. + * On success, the number of bytes should be returned, others + * excluding -ENOMSG mean error. -ENOMSG means to try next zone. + */ +struct pstore_device_info { + unsigned long total_size; + unsigned int flags; + pstore_zone_read_op read; + pstore_zone_write_op write; + pstore_zone_erase_op erase; + pstore_zone_write_op panic_write; +}; + +int register_pstore_device(struct pstore_device_info *dev); +void unregister_pstore_device(struct pstore_device_info *dev); + /** * struct pstore_blk_config - the pstore_blk backend configuration * diff --git a/include/linux/pstore_zone.h b/include/linux/pstore_zone.h index e79a18e41064..1e35eaa33e5e 100644 --- a/include/linux/pstore_zone.h +++ b/include/linux/pstore_zone.h @@ -7,6 +7,7 @@ typedef ssize_t (*pstore_zone_read_op)(char *, size_t, loff_t); typedef ssize_t (*pstore_zone_write_op)(const char *, size_t, loff_t); +typedef ssize_t (*pstore_zone_erase_op)(size_t, loff_t); /** * struct pstore_zone_info - pstore/zone back-end driver structure * @@ -27,6 +28,10 @@ typedef ssize_t (*pstore_zone_write_op)(const char *, size_t, loff_t); * @write: The same as @read, but the following error number: * -EBUSY means try to write again later. * -ENOMSG means to try next zone. + * @erase: The general erase operation for device with special removing + * job. Both of the function parameters @size and @offset are + * relative value to storage. + * Return 0 on success and others on failure. * @panic_write:The write operation only used for panic case. It's optional * if you do not care panic log. The parameters are relative * value to storage. @@ -45,6 +50,7 @@ struct pstore_zone_info { unsigned long ftrace_size; pstore_zone_read_op read; pstore_zone_write_op write; + pstore_zone_erase_op erase; pstore_zone_write_op panic_write; }; -- cgit v1.2.3 From 78c08247b9d3e03192f8b359aa079024e805a948 Mon Sep 17 00:00:00 2001 From: WeiXiong Liao Date: Wed, 25 Mar 2020 16:55:06 +0800 Subject: mtd: Support kmsg dumper based on pstore/blk This introduces mtdpstore, which is similar to mtdoops but more powerful. It uses pstore/blk, and aims to store panic and oops logs to a flash partition, where pstore can later read back and present as files in the mounted pstore filesystem. To make mtdpstore work, the "blkdev" of pstore/blk should be set as MTD device name or MTD device number. For more details, see Documentation/admin-guide/pstore-blk.rst This solves a number of issues: - Work duplication: both of pstore and mtdoops do the same job storing panic/oops log. They have very similar logic, registering to kmsg dumper and storing logs to several chunks one by one. - Layer violations: drivers should provides methods instead of polices. MTD should provide read/write/erase operations, and allow a higher level drivers to provide the chunk management, kmsg dump configuration, etc. - Missing features: pstore provides many additional features, including presenting the logs as files, logging dump time and count, and supporting other frontends like pmsg, console, etc. Signed-off-by: WeiXiong Liao Link: https://lore.kernel.org/lkml/20200511233229.27745-11-keescook@chromium.org/ Link: https://lore.kernel.org/r/1589266715-4168-1-git-send-email-liaoweixiong@allwinnertech.com Signed-off-by: Kees Cook --- Documentation/admin-guide/pstore-blk.rst | 9 +- drivers/mtd/Kconfig | 10 + drivers/mtd/Makefile | 1 + drivers/mtd/mtdpstore.c | 578 +++++++++++++++++++++++++++++++ 4 files changed, 596 insertions(+), 2 deletions(-) create mode 100644 drivers/mtd/mtdpstore.c (limited to 'Documentation/admin-guide') diff --git a/Documentation/admin-guide/pstore-blk.rst b/Documentation/admin-guide/pstore-blk.rst index d45341e55e82..296d5027787a 100644 --- a/Documentation/admin-guide/pstore-blk.rst +++ b/Documentation/admin-guide/pstore-blk.rst @@ -43,9 +43,9 @@ blkdev ~~~~~~ The block device to use. Most of the time, it is a partition of block device. -It's required for pstore/blk. +It's required for pstore/blk. It is also used for MTD device. -It accepts the following variants: +It accepts the following variants for block device: 1. device number in hexadecimal represents itself; no leading 0x, for example b302. @@ -64,6 +64,11 @@ It accepts the following variants: partition with a known unique id. #. : major and minor number of the device separated by a colon. +It accepts the following variants for MTD device: + +1. MTD device name. "pstore" is recommended. +#. MTD device number. + kmsg_size ~~~~~~~~~ diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig index 42d401ea60ee..6ddab796216d 100644 --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -170,6 +170,16 @@ config MTD_OOPS buffer in a flash partition where it can be read back at some later point. +config MTD_PSTORE + tristate "Log panic/oops to an MTD buffer based on pstore" + depends on PSTORE_BLK + help + This enables panic and oops messages to be logged to a circular + buffer in a flash partition where it can be read back as files after + mounting pstore filesystem. + + If unsure, say N. + config MTD_SWAP tristate "Swap on MTD device support" depends on MTD && SWAP diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile index 56cc60ccc477..593d0593a038 100644 --- a/drivers/mtd/Makefile +++ b/drivers/mtd/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_RFD_FTL) += rfd_ftl.o obj-$(CONFIG_SSFDC) += ssfdc.o obj-$(CONFIG_SM_FTL) += sm_ftl.o obj-$(CONFIG_MTD_OOPS) += mtdoops.o +obj-$(CONFIG_MTD_PSTORE) += mtdpstore.o obj-$(CONFIG_MTD_SWAP) += mtdswap.o nftl-objs := nftlcore.o nftlmount.o diff --git a/drivers/mtd/mtdpstore.c b/drivers/mtd/mtdpstore.c new file mode 100644 index 000000000000..a4fe6060b960 --- /dev/null +++ b/drivers/mtd/mtdpstore.c @@ -0,0 +1,578 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define dev_fmt(fmt) "mtdoops-pstore: " fmt + +#include +#include +#include +#include +#include + +static struct mtdpstore_context { + int index; + struct pstore_blk_config info; + struct pstore_device_info dev; + struct mtd_info *mtd; + unsigned long *rmmap; /* removed bit map */ + unsigned long *usedmap; /* used bit map */ + /* + * used for panic write + * As there are no block_isbad for panic case, we should keep this + * status before panic to ensure panic_write not failed. + */ + unsigned long *badmap; /* bad block bit map */ +} oops_cxt; + +static int mtdpstore_block_isbad(struct mtdpstore_context *cxt, loff_t off) +{ + int ret; + struct mtd_info *mtd = cxt->mtd; + u64 blknum; + + off = ALIGN_DOWN(off, mtd->erasesize); + blknum = div_u64(off, mtd->erasesize); + + if (test_bit(blknum, cxt->badmap)) + return true; + ret = mtd_block_isbad(mtd, off); + if (ret < 0) { + dev_err(&mtd->dev, "mtd_block_isbad failed, aborting\n"); + return ret; + } else if (ret > 0) { + set_bit(blknum, cxt->badmap); + return true; + } + return false; +} + +static inline int mtdpstore_panic_block_isbad(struct mtdpstore_context *cxt, + loff_t off) +{ + struct mtd_info *mtd = cxt->mtd; + u64 blknum; + + off = ALIGN_DOWN(off, mtd->erasesize); + blknum = div_u64(off, mtd->erasesize); + return test_bit(blknum, cxt->badmap); +} + +static inline void mtdpstore_mark_used(struct mtdpstore_context *cxt, + loff_t off) +{ + struct mtd_info *mtd = cxt->mtd; + u64 zonenum = div_u64(off, cxt->info.kmsg_size); + + dev_dbg(&mtd->dev, "mark zone %llu used\n", zonenum); + set_bit(zonenum, cxt->usedmap); +} + +static inline void mtdpstore_mark_unused(struct mtdpstore_context *cxt, + loff_t off) +{ + struct mtd_info *mtd = cxt->mtd; + u64 zonenum = div_u64(off, cxt->info.kmsg_size); + + dev_dbg(&mtd->dev, "mark zone %llu unused\n", zonenum); + clear_bit(zonenum, cxt->usedmap); +} + +static inline void mtdpstore_block_mark_unused(struct mtdpstore_context *cxt, + loff_t off) +{ + struct mtd_info *mtd = cxt->mtd; + u32 zonecnt = mtd->erasesize / cxt->info.kmsg_size; + u64 zonenum; + + off = ALIGN_DOWN(off, mtd->erasesize); + zonenum = div_u64(off, cxt->info.kmsg_size); + while (zonecnt > 0) { + dev_dbg(&mtd->dev, "mark zone %llu unused\n", zonenum); + clear_bit(zonenum, cxt->usedmap); + zonenum++; + zonecnt--; + } +} + +static inline int mtdpstore_is_used(struct mtdpstore_context *cxt, loff_t off) +{ + u64 zonenum = div_u64(off, cxt->info.kmsg_size); + u64 blknum = div_u64(off, cxt->mtd->erasesize); + + if (test_bit(blknum, cxt->badmap)) + return true; + return test_bit(zonenum, cxt->usedmap); +} + +static int mtdpstore_block_is_used(struct mtdpstore_context *cxt, + loff_t off) +{ + struct mtd_info *mtd = cxt->mtd; + u32 zonecnt = mtd->erasesize / cxt->info.kmsg_size; + u64 zonenum; + + off = ALIGN_DOWN(off, mtd->erasesize); + zonenum = div_u64(off, cxt->info.kmsg_size); + while (zonecnt > 0) { + if (test_bit(zonenum, cxt->usedmap)) + return true; + zonenum++; + zonecnt--; + } + return false; +} + +static int mtdpstore_is_empty(struct mtdpstore_context *cxt, char *buf, + size_t size) +{ + struct mtd_info *mtd = cxt->mtd; + size_t sz; + int i; + + sz = min_t(uint32_t, size, mtd->writesize / 4); + for (i = 0; i < sz; i++) { + if (buf[i] != (char)0xFF) + return false; + } + return true; +} + +static void mtdpstore_mark_removed(struct mtdpstore_context *cxt, loff_t off) +{ + struct mtd_info *mtd = cxt->mtd; + u64 zonenum = div_u64(off, cxt->info.kmsg_size); + + dev_dbg(&mtd->dev, "mark zone %llu removed\n", zonenum); + set_bit(zonenum, cxt->rmmap); +} + +static void mtdpstore_block_clear_removed(struct mtdpstore_context *cxt, + loff_t off) +{ + struct mtd_info *mtd = cxt->mtd; + u32 zonecnt = mtd->erasesize / cxt->info.kmsg_size; + u64 zonenum; + + off = ALIGN_DOWN(off, mtd->erasesize); + zonenum = div_u64(off, cxt->info.kmsg_size); + while (zonecnt > 0) { + clear_bit(zonenum, cxt->rmmap); + zonenum++; + zonecnt--; + } +} + +static int mtdpstore_block_is_removed(struct mtdpstore_context *cxt, + loff_t off) +{ + struct mtd_info *mtd = cxt->mtd; + u32 zonecnt = mtd->erasesize / cxt->info.kmsg_size; + u64 zonenum; + + off = ALIGN_DOWN(off, mtd->erasesize); + zonenum = div_u64(off, cxt->info.kmsg_size); + while (zonecnt > 0) { + if (test_bit(zonenum, cxt->rmmap)) + return true; + zonenum++; + zonecnt--; + } + return false; +} + +static int mtdpstore_erase_do(struct mtdpstore_context *cxt, loff_t off) +{ + struct mtd_info *mtd = cxt->mtd; + struct erase_info erase; + int ret; + + off = ALIGN_DOWN(off, cxt->mtd->erasesize); + dev_dbg(&mtd->dev, "try to erase off 0x%llx\n", off); + erase.len = cxt->mtd->erasesize; + erase.addr = off; + ret = mtd_erase(cxt->mtd, &erase); + if (!ret) + mtdpstore_block_clear_removed(cxt, off); + else + dev_err(&mtd->dev, "erase of region [0x%llx, 0x%llx] on \"%s\" failed\n", + (unsigned long long)erase.addr, + (unsigned long long)erase.len, cxt->info.device); + return ret; +} + +/* + * called while removing file + * + * Avoiding over erasing, do erase block only when the whole block is unused. + * If the block contains valid log, do erase lazily on flush_removed() when + * unregister. + */ +static ssize_t mtdpstore_erase(size_t size, loff_t off) +{ + struct mtdpstore_context *cxt = &oops_cxt; + + if (mtdpstore_block_isbad(cxt, off)) + return -EIO; + + mtdpstore_mark_unused(cxt, off); + + /* If the block still has valid data, mtdpstore do erase lazily */ + if (likely(mtdpstore_block_is_used(cxt, off))) { + mtdpstore_mark_removed(cxt, off); + return 0; + } + + /* all zones are unused, erase it */ + return mtdpstore_erase_do(cxt, off); +} + +/* + * What is security for mtdpstore? + * As there is no erase for panic case, we should ensure at least one zone + * is writable. Otherwise, panic write will fail. + * If zone is used, write operation will return -ENOMSG, which means that + * pstore/blk will try one by one until gets an empty zone. So, it is not + * needed to ensure the next zone is empty, but at least one. + */ +static int mtdpstore_security(struct mtdpstore_context *cxt, loff_t off) +{ + int ret = 0, i; + struct mtd_info *mtd = cxt->mtd; + u32 zonenum = (u32)div_u64(off, cxt->info.kmsg_size); + u32 zonecnt = (u32)div_u64(cxt->mtd->size, cxt->info.kmsg_size); + u32 blkcnt = (u32)div_u64(cxt->mtd->size, cxt->mtd->erasesize); + u32 erasesize = cxt->mtd->erasesize; + + for (i = 0; i < zonecnt; i++) { + u32 num = (zonenum + i) % zonecnt; + + /* found empty zone */ + if (!test_bit(num, cxt->usedmap)) + return 0; + } + + /* If there is no any empty zone, we have no way but to do erase */ + while (blkcnt--) { + div64_u64_rem(off + erasesize, cxt->mtd->size, (u64 *)&off); + + if (mtdpstore_block_isbad(cxt, off)) + continue; + + ret = mtdpstore_erase_do(cxt, off); + if (!ret) { + mtdpstore_block_mark_unused(cxt, off); + break; + } + } + + if (ret) + dev_err(&mtd->dev, "all blocks bad!\n"); + dev_dbg(&mtd->dev, "end security\n"); + return ret; +} + +static ssize_t mtdpstore_write(const char *buf, size_t size, loff_t off) +{ + struct mtdpstore_context *cxt = &oops_cxt; + struct mtd_info *mtd = cxt->mtd; + size_t retlen; + int ret; + + if (mtdpstore_block_isbad(cxt, off)) + return -ENOMSG; + + /* zone is used, please try next one */ + if (mtdpstore_is_used(cxt, off)) + return -ENOMSG; + + dev_dbg(&mtd->dev, "try to write off 0x%llx size %zu\n", off, size); + ret = mtd_write(cxt->mtd, off, size, &retlen, (u_char *)buf); + if (ret < 0 || retlen != size) { + dev_err(&mtd->dev, "write failure at %lld (%zu of %zu written), err %d\n", + off, retlen, size, ret); + return -EIO; + } + mtdpstore_mark_used(cxt, off); + + mtdpstore_security(cxt, off); + return retlen; +} + +static inline bool mtdpstore_is_io_error(int ret) +{ + return ret < 0 && !mtd_is_bitflip(ret) && !mtd_is_eccerr(ret); +} + +/* + * All zones will be read as pstore/blk will read zone one by one when do + * recover. + */ +static ssize_t mtdpstore_read(char *buf, size_t size, loff_t off) +{ + struct mtdpstore_context *cxt = &oops_cxt; + struct mtd_info *mtd = cxt->mtd; + size_t retlen, done; + int ret; + + if (mtdpstore_block_isbad(cxt, off)) + return -ENOMSG; + + dev_dbg(&mtd->dev, "try to read off 0x%llx size %zu\n", off, size); + for (done = 0, retlen = 0; done < size; done += retlen) { + retlen = 0; + + ret = mtd_read(cxt->mtd, off + done, size - done, &retlen, + (u_char *)buf + done); + if (mtdpstore_is_io_error(ret)) { + dev_err(&mtd->dev, "read failure at %lld (%zu of %zu read), err %d\n", + off + done, retlen, size - done, ret); + /* the zone may be broken, try next one */ + return -ENOMSG; + } + + /* + * ECC error. The impact on log data is so small. Maybe we can + * still read it and try to understand. So mtdpstore just hands + * over what it gets and user can judge whether the data is + * valid or not. + */ + if (mtd_is_eccerr(ret)) { + dev_err(&mtd->dev, "ecc error at %lld (%zu of %zu read), err %d\n", + off + done, retlen, size - done, ret); + /* driver may not set retlen when ecc error */ + retlen = retlen == 0 ? size - done : retlen; + } + } + + if (mtdpstore_is_empty(cxt, buf, size)) + mtdpstore_mark_unused(cxt, off); + else + mtdpstore_mark_used(cxt, off); + + mtdpstore_security(cxt, off); + return retlen; +} + +static ssize_t mtdpstore_panic_write(const char *buf, size_t size, loff_t off) +{ + struct mtdpstore_context *cxt = &oops_cxt; + struct mtd_info *mtd = cxt->mtd; + size_t retlen; + int ret; + + if (mtdpstore_panic_block_isbad(cxt, off)) + return -ENOMSG; + + /* zone is used, please try next one */ + if (mtdpstore_is_used(cxt, off)) + return -ENOMSG; + + ret = mtd_panic_write(cxt->mtd, off, size, &retlen, (u_char *)buf); + if (ret < 0 || size != retlen) { + dev_err(&mtd->dev, "panic write failure at %lld (%zu of %zu read), err %d\n", + off, retlen, size, ret); + return -EIO; + } + mtdpstore_mark_used(cxt, off); + + return retlen; +} + +static void mtdpstore_notify_add(struct mtd_info *mtd) +{ + int ret; + struct mtdpstore_context *cxt = &oops_cxt; + struct pstore_blk_config *info = &cxt->info; + unsigned long longcnt; + + if (!strcmp(mtd->name, info->device)) + cxt->index = mtd->index; + + if (mtd->index != cxt->index || cxt->index < 0) + return; + + dev_dbg(&mtd->dev, "found matching MTD device %s\n", mtd->name); + + if (mtd->size < info->kmsg_size * 2) { + dev_err(&mtd->dev, "MTD partition %d not big enough\n", + mtd->index); + return; + } + /* + * kmsg_size must be aligned to 4096 Bytes, which is limited by + * psblk. The default value of kmsg_size is 64KB. If kmsg_size + * is larger than erasesize, some errors will occur since mtdpsotre + * is designed on it. + */ + if (mtd->erasesize < info->kmsg_size) { + dev_err(&mtd->dev, "eraseblock size of MTD partition %d too small\n", + mtd->index); + return; + } + if (unlikely(info->kmsg_size % mtd->writesize)) { + dev_err(&mtd->dev, "record size %lu KB must align to write size %d KB\n", + info->kmsg_size / 1024, + mtd->writesize / 1024); + return; + } + + longcnt = BITS_TO_LONGS(div_u64(mtd->size, info->kmsg_size)); + cxt->rmmap = kcalloc(longcnt, sizeof(long), GFP_KERNEL); + cxt->usedmap = kcalloc(longcnt, sizeof(long), GFP_KERNEL); + + longcnt = BITS_TO_LONGS(div_u64(mtd->size, mtd->erasesize)); + cxt->badmap = kcalloc(longcnt, sizeof(long), GFP_KERNEL); + + cxt->dev.total_size = mtd->size; + /* just support dmesg right now */ + cxt->dev.flags = PSTORE_FLAGS_DMESG; + cxt->dev.read = mtdpstore_read; + cxt->dev.write = mtdpstore_write; + cxt->dev.erase = mtdpstore_erase; + cxt->dev.panic_write = mtdpstore_panic_write; + + ret = register_pstore_device(&cxt->dev); + if (ret) { + dev_err(&mtd->dev, "mtd%d register to psblk failed\n", + mtd->index); + return; + } + cxt->mtd = mtd; + dev_info(&mtd->dev, "Attached to MTD device %d\n", mtd->index); +} + +static int mtdpstore_flush_removed_do(struct mtdpstore_context *cxt, + loff_t off, size_t size) +{ + struct mtd_info *mtd = cxt->mtd; + u_char *buf; + int ret; + size_t retlen; + struct erase_info erase; + + buf = kmalloc(mtd->erasesize, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + /* 1st. read to cache */ + ret = mtd_read(mtd, off, mtd->erasesize, &retlen, buf); + if (mtdpstore_is_io_error(ret)) + goto free; + + /* 2nd. erase block */ + erase.len = mtd->erasesize; + erase.addr = off; + ret = mtd_erase(mtd, &erase); + if (ret) + goto free; + + /* 3rd. write back */ + while (size) { + unsigned int zonesize = cxt->info.kmsg_size; + + /* there is valid data on block, write back */ + if (mtdpstore_is_used(cxt, off)) { + ret = mtd_write(mtd, off, zonesize, &retlen, buf); + if (ret) + dev_err(&mtd->dev, "write failure at %lld (%zu of %u written), err %d\n", + off, retlen, zonesize, ret); + } + + off += zonesize; + size -= min_t(unsigned int, zonesize, size); + } + +free: + kfree(buf); + return ret; +} + +/* + * What does mtdpstore_flush_removed() do? + * When user remove any log file on pstore filesystem, mtdpstore should do + * something to ensure log file removed. If the whole block is no longer used, + * it's nice to erase the block. However if the block still contains valid log, + * what mtdpstore can do is to erase and write the valid log back. + */ +static int mtdpstore_flush_removed(struct mtdpstore_context *cxt) +{ + struct mtd_info *mtd = cxt->mtd; + int ret; + loff_t off; + u32 blkcnt = (u32)div_u64(mtd->size, mtd->erasesize); + + for (off = 0; blkcnt > 0; blkcnt--, off += mtd->erasesize) { + ret = mtdpstore_block_isbad(cxt, off); + if (ret) + continue; + + ret = mtdpstore_block_is_removed(cxt, off); + if (!ret) + continue; + + ret = mtdpstore_flush_removed_do(cxt, off, mtd->erasesize); + if (ret) + return ret; + } + return 0; +} + +static void mtdpstore_notify_remove(struct mtd_info *mtd) +{ + struct mtdpstore_context *cxt = &oops_cxt; + + if (mtd->index != cxt->index || cxt->index < 0) + return; + + mtdpstore_flush_removed(cxt); + + unregister_pstore_device(&cxt->dev); + kfree(cxt->badmap); + kfree(cxt->usedmap); + kfree(cxt->rmmap); + cxt->mtd = NULL; + cxt->index = -1; +} + +static struct mtd_notifier mtdpstore_notifier = { + .add = mtdpstore_notify_add, + .remove = mtdpstore_notify_remove, +}; + +static int __init mtdpstore_init(void) +{ + int ret; + struct mtdpstore_context *cxt = &oops_cxt; + struct pstore_blk_config *info = &cxt->info; + + ret = pstore_blk_get_config(info); + if (unlikely(ret)) + return ret; + + if (strlen(info->device) == 0) { + pr_err("mtd device must be supplied (device name is empty)\n"); + return -EINVAL; + } + if (!info->kmsg_size) { + pr_err("no backend enabled (kmsg_size is 0)\n"); + return -EINVAL; + } + + /* Setup the MTD device to use */ + ret = kstrtoint((char *)info->device, 0, &cxt->index); + if (ret) + cxt->index = -1; + + register_mtd_user(&mtdpstore_notifier); + return 0; +} +module_init(mtdpstore_init); + +static void __exit mtdpstore_exit(void) +{ + unregister_mtd_user(&mtdpstore_notifier); +} +module_exit(mtdpstore_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("WeiXiong Liao "); +MODULE_DESCRIPTION("MTD backend for pstore/blk"); -- cgit v1.2.3