From nobody Sun May 24 22:35:57 2026 Received: from mail-ej1-f74.google.com (mail-ej1-f74.google.com [209.85.218.74]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B528036D9EA for ; Wed, 20 May 2026 17:43:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.74 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779299038; cv=none; b=sBkSJQAACPQidV3XSLkGXyBw74Kkfp9qsfXQI7GOwT/w45H3Vh5DfPtvqhvZPENlpQKpjEFACdrxc4HuB2/wPsPQFUzknhFM7InzKG/A2uL3dsK8tgw0+DJoIQk6eIG1XYNXZK5IrpMFiFBmMKcV9QPanzH4T0GnGU58MRDlObU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779299038; c=relaxed/simple; bh=/QppHQ4l/CYX8cdmOQsJdBtGY+UYxdYufmhZC3baT6g=; h=Date:Mime-Version:Message-ID:Subject:From:To:Cc:Content-Type; b=NWrWkPRMBSnKxEnmpdVZjGCeaeDya9+VtF3VMMaPeboIMnV9Y/HG/LkXuusQOjnJzaDdRZXczzhCF+YSE8Lc/jGefJrF2G0VLF4CbZnkOpNKKbmYg3KdH+NXQkn943ZwFF3ZjMowiMyqrLXW6Mc/WxW0n1oN8S8Cd0evJJNxvUA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--mclapinski.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=GrIJrT5K; arc=none smtp.client-ip=209.85.218.74 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--mclapinski.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="GrIJrT5K" Received: by mail-ej1-f74.google.com with SMTP id a640c23a62f3a-bd52e681e03so395678866b.1 for ; Wed, 20 May 2026 10:43:56 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1779299035; x=1779903835; darn=vger.kernel.org; h=cc:to:from:subject:message-id:mime-version:date:from:to:cc:subject :date:message-id:reply-to; bh=0v72zAjpVoNGtu4uzPAqcHvbj8JBWjnbgBGdrtKjqFE=; b=GrIJrT5K7BxhagCChzJU3JfwxdTPCOnLXW211SVoW93Ibg28ACkUCft8XSbUwBcQ7Z Py6Z1i2RFJp259K3bvf21Ix0H/XQsjS1GxQn2LZPn5yLqZUUF1DFRTGx+tjhbMgt9NqN YyP7MPWNg2NfuZFfqiPUfwSF2ja7sHN7E7uyp8HK/AcTECKvPC7jQ1aDCwK/ceflbo4o /62EDYCN4332b0+rYHekL9MJHjV5/VIN5l/QgUMH2Nrbm4fKKyHRTYCccdgBx9/MYK83 cE1WxF/8jgo/RcqdtGgPAKTUJ4CdMLjVNxC7EgcQpWwadrVPqSd4HpxIQOKKHtV28H7Z js7Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779299035; x=1779903835; h=cc:to:from:subject:message-id:mime-version:date:x-gm-message-state :from:to:cc:subject:date:message-id:reply-to; bh=0v72zAjpVoNGtu4uzPAqcHvbj8JBWjnbgBGdrtKjqFE=; b=E4QuB7fZTarkhFvCrtL7Ujme0k51ex1PWZESQzCu+hfUBeElotqtVMNvRhER6XfYAp zep32iKphaV79dfkrWrOSN1EwYbm8vOqbvraPRFWUUSr+t/1lHTJ1G+zBcqj3reUKBo6 gSJcoJ/bo652rAOakzmj4ThTKJ8ZCwpA6cFVF60MUoy7gJD9U3vUsuU1POTCQAiDjrBY T8+fwGySGqDpNc3sYcH8E57WPyN41hdlDAyyEeL6dHVHn+VgEc1ZbWc1HX1quFbLvHwL IjZceamLs/PhkUtmMAku0cb9FkSet85EgX+ZlLxcWHjfK2y5FiP3sIlIPRh7ap+Plfw0 802g== X-Forwarded-Encrypted: i=1; AFNElJ+wgBHZctpM9Qu+TtabHlRa66vwwDSNoTTbMePhQsyzlSKKKUgPFjmd6gF+ufZ9zzFYeRiwYj19y0kzfLI=@vger.kernel.org X-Gm-Message-State: AOJu0YxuJCGJpDlO64Bi/Znm+MzlMrZmI2EFXZXEzMKtJFRjvVOZ0iDx ZooDwqCUp7II4Aj54dfyhM4F2Cb9AbgYSkAOglfdnhNrROtfkZEh7F4OoHhYj7spgwrOt1ZxohR RjOG2alIFT0PZ9L2hCwEw8w== X-Received: from ejcdl17.prod.google.com ([2002:a17:907:9451:b0:bc6:4cec:2704]) (user=mclapinski job=prod-delivery.src-stubby-dispatcher) by 2002:a17:907:cc03:b0:bdb:a45a:c2de with SMTP id a640c23a62f3a-bdba45acccdmr45497666b.38.1779299034859; Wed, 20 May 2026 10:43:54 -0700 (PDT) Date: Wed, 20 May 2026 19:43:32 +0200 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 X-Mailer: git-send-email 2.54.0.669.g59709faab0-goog Message-ID: <20260520174332.921186-1-mclapinski@google.com> Subject: [PATCH] pstore: add a KHO backend From: Michal Clapinski To: Kees Cook , Tony Luck , "Guilherme G. Piccoli" , Pasha Tatashin , Mike Rapoport , Pratyush Yadav , Alexander Graf , linux-kernel@vger.kernel.org, kexec@lists.infradead.org Cc: Michal Clapinski Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Up to this point to preserve late shutdown logs in memory, users had to predefine a memory region using ramoops. This commit changes this by preserving a buffer using kexec-handover. For now it supports preserving only 1 dmesg buffer. It gets replaced with the new buffer on every kexec, so the user has to copy the file out of pstore after every kexec. There is no erase() support. Signed-off-by: Michal Clapinski --- fs/pstore/Kconfig | 9 ++ fs/pstore/Makefile | 2 + fs/pstore/pstore_kho.c | 267 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 278 insertions(+) create mode 100644 fs/pstore/pstore_kho.c diff --git a/fs/pstore/Kconfig b/fs/pstore/Kconfig index 3acc38600cd1..70853799e211 100644 --- a/fs/pstore/Kconfig +++ b/fs/pstore/Kconfig @@ -81,6 +81,15 @@ config PSTORE_RAM =20 For more information, see Documentation/admin-guide/ramoops.rst. =20 +config PSTORE_KHO + tristate "Preserve logs over kexec" + depends on PSTORE + depends on KEXEC_HANDOVER + help + A pstore backend for preserving dmesg over KHO (kexec handover). + + It supports preservation of only 1 dmesg file. + config PSTORE_ZONE tristate depends on PSTORE diff --git a/fs/pstore/Makefile b/fs/pstore/Makefile index c270467aeece..518cd408bf8e 100644 --- a/fs/pstore/Makefile +++ b/fs/pstore/Makefile @@ -13,6 +13,8 @@ pstore-$(CONFIG_PSTORE_PMSG) +=3D pmsg.o ramoops-objs +=3D ram.o ram_core.o obj-$(CONFIG_PSTORE_RAM) +=3D ramoops.o =20 +obj-$(CONFIG_PSTORE_KHO) +=3D pstore_kho.o + pstore_zone-objs +=3D zone.o obj-$(CONFIG_PSTORE_ZONE) +=3D pstore_zone.o =20 diff --git a/fs/pstore/pstore_kho.c b/fs/pstore/pstore_kho.c new file mode 100644 index 000000000000..3bc679273c8d --- /dev/null +++ b/fs/pstore/pstore_kho.c @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * KHO (Kexec Handover) backend for pstore + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#define KHO_PSTORE_FDT_NAME "pstore-kho" +#define KHO_PSTORE_COMPAT "pstore-kho-v1" +#define KHO_PSTORE_DATA "pstore_kho_record" + +static const size_t record_max_size =3D 1 << CONFIG_LOG_BUF_SHIFT; + +struct pstore_kho_record { + ssize_t size; + struct timespec64 time; + int count; + enum kmsg_dump_reason reason; + unsigned int part; + bool compressed; + char buf[]; +}; + +struct pstore_kho_context { + struct pstore_info pstore; + bool read_done; +}; + +static struct pstore_kho_record *kho_data_in; +static struct pstore_kho_record *kho_data_out; +static void *fdt_out; + +static int pstore_kho_open(struct pstore_info *psi) +{ + struct pstore_kho_context *cxt =3D psi->data; + + cxt->read_done =3D false; + return 0; +} + +static ssize_t pstore_kho_read(struct pstore_record *record) +{ + struct pstore_kho_context *cxt =3D record->psi->data; + + if (cxt->read_done) + return 0; + + cxt->read_done =3D true; + + if (!kho_data_in || !kho_data_in->size) + return 0; + + record->buf =3D kmemdup(kho_data_in->buf, kho_data_in->size, GFP_KERNEL); + if (!record->buf) + return -ENOMEM; + + record->type =3D PSTORE_TYPE_DMESG; + record->id =3D 0; + record->size =3D kho_data_in->size; + record->time =3D kho_data_in->time; + record->count =3D kho_data_in->count; + record->reason =3D kho_data_in->reason; + record->part =3D kho_data_in->part; + record->compressed =3D kho_data_in->compressed; + + return record->size; +} + +static int pstore_kho_write(struct pstore_record *record) +{ + if (record->type !=3D PSTORE_TYPE_DMESG) + return -EINVAL; + + if (kho_data_out->size !=3D 0) { + pr_err("pstore kho already contains a record\n"); + return -ENOSPC; + } + + if (record->size > record_max_size) { + pr_err("dmesg record too big, record size: %lu, available space: %lu\n", + record->size, record_max_size); + return -ENOSPC; + } + + memcpy(kho_data_out->buf, record->buf, record->size); + kho_data_out->size =3D record->size; + kho_data_out->time =3D record->time; + kho_data_out->count =3D record->count; + kho_data_out->reason =3D record->reason; + kho_data_out->part =3D record->part; + kho_data_out->compressed =3D record->compressed; + + return 0; +} + +static struct pstore_kho_context pstore_kho_cxt =3D { + .pstore =3D { + .owner =3D THIS_MODULE, + .name =3D "kho", + .bufsize =3D record_max_size, + .flags =3D PSTORE_FLAGS_DMESG, + .max_reason =3D KMSG_DUMP_SHUTDOWN, + .open =3D pstore_kho_open, + .read =3D pstore_kho_read, + .write =3D pstore_kho_write, + }, +}; + +static void __init kho_setup_incoming(void) +{ + phys_addr_t fdt_phys; + void *fdt_in; + const phys_addr_t *kho_data_phys; + int err, len; + + err =3D kho_retrieve_subtree(KHO_PSTORE_FDT_NAME, &fdt_phys); + if (err) { + if (err !=3D -ENOENT) + pr_err("failed to retrieve FDT %s from KHO: %d\n", + KHO_PSTORE_FDT_NAME, err); + return; + } + + fdt_in =3D phys_to_virt(fdt_phys); + err =3D fdt_node_check_compatible(fdt_in, 0, KHO_PSTORE_COMPAT); + if (err) { + pr_err("FDT '%s' is incompatible with '%s' [%d]\n", + KHO_PSTORE_FDT_NAME, KHO_PSTORE_COMPAT, err); + return; + } + + kho_data_phys =3D fdt_getprop(fdt_in, 0, KHO_PSTORE_DATA, &len); + if (!kho_data_phys || len !=3D sizeof(*kho_data_phys)) { + pr_err("failed to find kho property %s\n", KHO_PSTORE_DATA); + return; + } + + kho_data_in =3D phys_to_virt(get_unaligned(kho_data_phys)); + + pr_info("successfully restored preserved data\n"); +} + +static int __init kho_setup_outgoing(void) +{ + phys_addr_t header_ser_phys; + int err; + size_t total_size =3D sizeof(struct pstore_kho_record) + record_max_size; + + kho_data_out =3D kho_alloc_preserve(total_size); + if (IS_ERR(kho_data_out)) { + pr_err("failed to allocate pstore kho data\n"); + return PTR_ERR(kho_data_out); + } + + fdt_out =3D kho_alloc_preserve(PAGE_SIZE); + if (IS_ERR(fdt_out)) { + pr_err("failed to allocate/preserve FDT memory\n"); + err =3D PTR_ERR(fdt_out); + goto err_free_header; + } + + err =3D fdt_create(fdt_out, PAGE_SIZE); + err |=3D fdt_finish_reservemap(fdt_out); + err |=3D fdt_begin_node(fdt_out, ""); + err |=3D fdt_property_string(fdt_out, "compatible", KHO_PSTORE_COMPAT); + + header_ser_phys =3D virt_to_phys(kho_data_out); + err |=3D fdt_property(fdt_out, KHO_PSTORE_DATA, &header_ser_phys, + sizeof(header_ser_phys)); + err |=3D fdt_end_node(fdt_out); + err |=3D fdt_finish(fdt_out); + if (err) { + pr_err("failed to configure fdt tree\n"); + goto err_free_fdt; + } + + err =3D kho_add_subtree(KHO_PSTORE_FDT_NAME, fdt_out); + if (err) { + pr_err("failed to add subtree\n"); + goto err_free_fdt; + } + + return 0; + +err_free_fdt: + kho_unpreserve_free(fdt_out); + +err_free_header: + kho_unpreserve_free(kho_data_out); + return err; +} + +static int __init pstore_kho_init(void) +{ + int err; + struct pstore_kho_context *cxt =3D &pstore_kho_cxt; + + if (!kho_is_enabled()) { + pr_info("KHO is disabled, pstore_kho cannot start\n"); + return -ENODEV; + } + + kho_setup_incoming(); + err =3D kho_setup_outgoing(); + if (err) { + pr_err("failed to setup outgoing KHO\n"); + goto err_free_incoming; + } + + cxt->pstore.data =3D cxt; + cxt->pstore.buf =3D kmalloc(cxt->pstore.bufsize, GFP_KERNEL); + if (!cxt->pstore.buf) { + err =3D -ENOMEM; + goto err_free_outgoing; + } + + err =3D pstore_register(&cxt->pstore); + if (err) { + pr_err("failed to register with pstore\n"); + goto err_free_pstore_buf; + } + + return 0; + +err_free_pstore_buf: + kfree(cxt->pstore.buf); + +err_free_outgoing: + kho_remove_subtree(fdt_out); + kho_unpreserve_free(fdt_out); + + kho_unpreserve_free(kho_data_out); + +err_free_incoming: + if (kho_data_in) + kho_restore_free(kho_data_in); + + return err; +} +module_init(pstore_kho_init); + +static void __exit pstore_kho_exit(void) +{ + pstore_unregister(&pstore_kho_cxt.pstore); + kfree(pstore_kho_cxt.pstore.buf); + + kho_remove_subtree(fdt_out); + kho_unpreserve_free(fdt_out); + + kho_unpreserve_free(kho_data_out); + + if (kho_data_in) + kho_restore_free(kho_data_in); +} +module_exit(pstore_kho_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Pstore backend for dmesg preservation over kexec"); --=20 2.54.0.669.g59709faab0-goog