From nobody Sat Apr 4 03:19:57 2026 Received: from mail-pj1-f99.google.com (mail-pj1-f99.google.com [209.85.216.99]) (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 08A942C0F81 for ; Fri, 20 Mar 2026 20:33:46 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.99 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774038828; cv=none; b=RXKqetTL03xch6HEE+39t3XHbyrnAcOXgmyqQEu+LtqM4NFRMfnFuw2rYfZLtjznJOP2A0LizAEEz+LNwaM6YbCPm7h4XqicsYZxsce/JgwDjCS2wLJK+kpm6omPyCCeUt+3A4JKk6ADT3pewwkY7Z8Yu54TQqADjqCicv25kBo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774038828; c=relaxed/simple; bh=qtVua69sP2G9xkMsjuSNOM/NL0Z1s1ViGHEt7N7dPys=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=IFNh9LVgXLltcMp/UTg/3EBZcoRP1FEbzPqlnM/zOwGlax8QCPXD2jgEROLEQHk/BLkGTbwt4RUEP3vZGGft5sM52cy8CDmiQbhf27y2GktDJixXDfETIaN7pjuYZ1EaJK+0ogTPgx5IDOt9NJ6Rwe/O0v1BtURBzDthhmnI4fg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=broadcom.com; spf=fail smtp.mailfrom=broadcom.com; dkim=pass (1024-bit key) header.d=broadcom.com header.i=@broadcom.com header.b=F/Fpy8E3; arc=none smtp.client-ip=209.85.216.99 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=broadcom.com Authentication-Results: smtp.subspace.kernel.org; spf=fail smtp.mailfrom=broadcom.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=broadcom.com header.i=@broadcom.com header.b="F/Fpy8E3" Received: by mail-pj1-f99.google.com with SMTP id 98e67ed59e1d1-35a1f3f07ebso504167a91.3 for ; Fri, 20 Mar 2026 13:33:46 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774038826; x=1774643626; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:dkim-signature:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=qrhXx6wPTVD4bGLE4pIlNQPG1YgkwhB8X1TOJlLEBbo=; b=TL2EOb56oTdXEfjpn8SCGAmGSF/gwxOAnz2ZRZS8XwI+NSUseyXuLmhHu/dG3EveVT rV1j1TNXdJsqUzq6hIwHDAAFmpDEE3+oNO5zcbZ2ilRxuoDxZ2j/I6vIOS9wVL0nTKrv 3aLLbOGTX86SG4M9RY/EUx4kkmhhUfT4pHwVtLfRuUgdDs10vsHIAiAG6kl40MDE6yoU hMmfytWi/WLIdRFvwCcre9KTWH1g3wFOskLOIZ5vNS4LeK5q4QRMTQhqIncZPaO/2n9H YCNiq+u9RwC4nulTOcKkEebIuP7rGse6eucOLWC/4NGr/NS9Dq4VE6RswDrjD6ouV9mc 4N6g== X-Forwarded-Encrypted: i=1; AJvYcCVrjNG8Y4i4xU3RHRkzLVIIyRcgLxl3pEECDgSiGOhJOBnow9Oo5YYs7Q+VpcXF22jNFNqzmRbKw2/4N7g=@vger.kernel.org X-Gm-Message-State: AOJu0Ywt4mb0Osku4ZG8iNgHttFjsvc7RdJYNAhdywkwasAddkuk9bDb rY4K+xVVu4IBaWOIAMxX+P2tBtWQSCNgBbMZBGAykQ5qAGpE5Pi92MqiztLmOfPZO9PiSeW3Kjf y8j4GTGr67NkxcI0KxqqToy9GnIl2EbrXGwPbccvyy0WsxXEGVwTfs9IC9nFzPn/SCyzHyGj49L lFjgPW2NVv0xYHMQ9FwU7U8cAkff/ImJFjpljqWouVmcMR/HysZQNnPrqW4pmkvMozTSXbkQpLa WD4QA74aB3st6gF X-Gm-Gg: ATEYQzyCMR6QNheKHn5azzaAUtuxWFeLRjfvL81m2tC+rZneDKnhTE3D1j/po+Tjmmm 7Nzq/otQmRu898Mnyc4qKJ7hSpZkeJ0gzcMNPy0Rz/TUbGmiym3+R1Nx0MyS0RLvNXG+xnZh1pO GuXuSo9Xj8CgVDEOQTBonFr+38px2y+Lw7+q54PbuE8Tu9mMKBAUsNTHesKNfYVGOMeRA1sdRZW 27uz+Uub34LCXsWOnmNyJya7gKd/eeix4TbBz91aUZSFK+FZ5oyDgXzEkX46qBy462iqBI9lhrO Mr7nwg/30ATNT/3m3nh6v4fPVS86Q8nVHZm5RB1ZHXwaWkR7n1caNeQYwNC5A4x/nzkQ2g/cVyk j8nEh4UwjDGAtccUAXm5f1arEKsRwBZ7eM0iF5E8mskrvL4Eua3ZMn8y9Gt5f0Fdc+TaeuX6Cyq qZZn4sv/DalrGc8h1HSdabLSHVLzGmpIWEZcxdTa9MhIJsxunlS7W/mgQL X-Received: by 2002:a17:90b:3b8a:b0:356:24f0:af08 with SMTP id 98e67ed59e1d1-35bd2b9df81mr3428474a91.1.1774038826253; Fri, 20 Mar 2026 13:33:46 -0700 (PDT) Received: from smtp-us-east1-p01-i01-si01.dlp.protect.broadcom.com (address-144-49-247-117.dlp.protect.broadcom.com. [144.49.247.117]) by smtp-relay.gmail.com with ESMTPS id 98e67ed59e1d1-35bd4009fabsm305347a91.5.2026.03.20.13.33.45 for (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Fri, 20 Mar 2026 13:33:46 -0700 (PDT) X-Relaying-Domain: broadcom.com X-CFilter-Loop: Reflected Received: by mail-qk1-f198.google.com with SMTP id af79cd13be357-8cb52a9c0eeso1018623185a.2 for ; Fri, 20 Mar 2026 13:33:44 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=broadcom.com; s=google; t=1774038824; x=1774643624; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=qrhXx6wPTVD4bGLE4pIlNQPG1YgkwhB8X1TOJlLEBbo=; b=F/Fpy8E37/t6FoTQpPZFFPhtdHsFuCqx7d3MbnZIsUA6hq2dz2BFRrUOx2ysqde6py 9JyVvegu0TOHbZnDXrZekSmlYB7iIyFztvNcsNyrL+GC34CfmvsXI4yFXdtM9B+8RLxW 4vxP2XHo4Cv8rSAmAhhzn5pNHVW3jdd+MvRS4= X-Forwarded-Encrypted: i=1; AJvYcCXjFtgx7arj1/Kzyh/CS8q7x+gZFsRJYIwMdhxTcihgq4wHJpB8x55gdeOZFHqRMnIONT6k5zpJ3fsvqb4=@vger.kernel.org X-Received: by 2002:a05:620a:29d0:b0:8cf:c4d7:dfa with SMTP id af79cd13be357-8cfc7e65890mr675354285a.16.1774038824494; Fri, 20 Mar 2026 13:33:44 -0700 (PDT) X-Received: by 2002:a05:620a:29d0:b0:8cf:c4d7:dfa with SMTP id af79cd13be357-8cfc7e65890mr675349885a.16.1774038823976; Fri, 20 Mar 2026 13:33:43 -0700 (PDT) Received: from mail.broadcom.net ([192.19.144.250]) by smtp.gmail.com with ESMTPSA id af79cd13be357-8cfc8fb9c10sm280699985a.19.2026.03.20.13.33.42 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 20 Mar 2026 13:33:43 -0700 (PDT) From: Kamal Dasu To: Ulf Hansson , Adrian Hunter , Kees Cook Cc: Tony Luck , "Guilherme G . Piccoli" , Florian Fainelli , Arend van Spriel , William Zhang , bcm-kernel-feedback-list@broadcom.com, linux-mmc@vger.kernel.org, linux-kernel@vger.kernel.org, Kamal Dasu Subject: [PATCH v4 1/4] mmc: core: Add panic-context host operations for pstore backends Date: Fri, 20 Mar 2026 16:32:46 -0400 Message-Id: <20260320203249.1965044-2-kamal.dasu@broadcom.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260320203249.1965044-1-kamal.dasu@broadcom.com> References: <20260320203249.1965044-1-kamal.dasu@broadcom.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-DetectorID-Processed: b00c1d49-9d2e-4205-b15f-d015386d3d5e Content-Type: text/plain; charset="utf-8" Add three new optional callbacks to struct mmc_host_ops for panic-safe MMC I/O: - panic_prepare: drain in-flight requests and prepare for polled I/O - panic_poll_completion: poll for request completion without interrupts - panic_complete: restore normal host state after panic I/O Add mmc_panic_claim_host() which uses WRITE_ONCE() to claim the host without taking the spin lock, since during panic other CPUs are stopped and may hold the lock. Signed-off-by: Kamal Dasu --- drivers/mmc/core/core.c | 23 +++++++++++++++++++++++ include/linux/mmc/host.h | 12 ++++++++++++ 2 files changed, 35 insertions(+) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 860378bea557..5326b246b4f2 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -860,6 +860,29 @@ void mmc_release_host(struct mmc_host *host) } EXPORT_SYMBOL(mmc_release_host); =20 +/** + * mmc_panic_claim_host - force-claim a host in panic context + * @host: mmc host to claim + * + * Force-claims the MMC host without locking. During kernel panic + * other CPUs are stopped and may be holding mmc_host->lock (e.g. + * inside __mmc_claim_host or mmc_release_host). Unlike sdhci_host->lock + * which is freed by the hardware drain+reset, mmc_host->lock has no + * hardware counterpart, so we must bypass it with WRITE_ONCE. + */ +void mmc_panic_claim_host(struct mmc_host *host) +{ + if (!host) + return; + + WRITE_ONCE(host->claimed, 1); + host->claimer =3D &host->default_ctx; + host->claimer->task =3D current; + WRITE_ONCE(host->claim_cnt, 1); + WRITE_ONCE(host->ongoing_mrq, NULL); +} +EXPORT_SYMBOL(mmc_panic_claim_host); + /* * This is a helper function, which fetches a runtime pm reference for the * card device and also claims the host. diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index ba84f02c2a10..cd44b62d29cb 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -272,6 +272,16 @@ struct mmc_host_ops { * negative errno in case of a failure or zero for success. */ int (*uhs2_control)(struct mmc_host *host, enum sd_uhs2_operation op); + + /* + * Optional panic-context ops for pstore backends that write to MMC + * during kernel panic with interrupts disabled. + */ + int (*panic_prepare)(struct mmc_host *host); + bool (*panic_poll_completion)(struct mmc_host *host, + struct mmc_request *mrq); + void (*panic_complete)(struct mmc_host *host, + struct mmc_request *mrq); }; =20 struct mmc_cqe_ops { @@ -758,4 +768,6 @@ int mmc_send_abort_tuning(struct mmc_host *host, u32 op= code); int mmc_get_ext_csd(struct mmc_card *card, u8 **new_ext_csd); int mmc_read_tuning(struct mmc_host *host, unsigned int blksz, unsigned in= t blocks); =20 +void mmc_panic_claim_host(struct mmc_host *host); + #endif /* LINUX_MMC_HOST_H */ --=20 2.34.1 From nobody Sat Apr 4 03:19:57 2026 Received: from mail-oa1-f99.google.com (mail-oa1-f99.google.com [209.85.160.99]) (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 62C8534FF58 for ; Fri, 20 Mar 2026 20:33:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.160.99 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774038830; cv=none; b=MyezGz7ekKrtmZC3c14hUT7841Hdwc7Nl3md4DeB5OeXyPRdMiutJKP+HkCaNueOdOLlosCWD5yE6PNRfICT2PHjK8ZMRnqemoAplZY+pseACS3yNFerftpfAFQHgiiMf+Zi/d8Eb0DLFnEDnpoSCLMibi8hZXvFlcsj/XzI6Yg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774038830; c=relaxed/simple; bh=8WmmNAFWdVy8iylQQm9cVlM3+5qqTYwPxJMPlafbX9Y=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=M1+x73Auo5uQK7KpyOjOHTYWJEW59HmBpw3TisV/h+h+n3gLVhKULAD7q/6XNkJQa6c9Vkh8jGUiRQr/m2sp7l5UVu13bskuGG9ZBqJDcKvHWjX4QnScOUkUxGnNkuINgudE1lZBCLFo7FiGAynhp0gflC85WLEt/smvB3h3VBI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=broadcom.com; spf=fail smtp.mailfrom=broadcom.com; dkim=pass (1024-bit key) header.d=broadcom.com header.i=@broadcom.com header.b=faaQK0pw; arc=none smtp.client-ip=209.85.160.99 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=broadcom.com Authentication-Results: smtp.subspace.kernel.org; spf=fail smtp.mailfrom=broadcom.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=broadcom.com header.i=@broadcom.com header.b="faaQK0pw" Received: by mail-oa1-f99.google.com with SMTP id 586e51a60fabf-40f0e14b9f9so1817015fac.1 for ; Fri, 20 Mar 2026 13:33:49 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774038828; x=1774643628; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:dkim-signature:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=1CXtTypYyHqjl2V69Ghw/md4zg3U7POaN7Ulg0LlUBU=; b=hM6flMrC5+DP3BBUT9PJTmBPA0cKSlpXJa6hmIBvuCNwGU/NjCqpqMVaatqi7m75cT BE1J/UFCRl1f9OJ+PNiwK8dZ3PSvSHrnwrmA1KZ7No996XacryqeU6iprrjXMj8nDvUE rnE+kEuAtUo93+ImKFvqB1fRnq4IdEz+jUhupXLk/BlaPCgq5ChkGIR+znzs4I5cr902 q+rW56olXFIyrfHA+px7vdtgt9iNOXjRoPLq6IDHEfM5B3ZLj49MsRViJ6yh68w6jcR3 W+AVk62rAZV1MfsPcBzHntQJVWa4oe8jIyCPVvToNA6UZFTxIU1x+d4L394Z4GRrTjw3 pVpA== X-Forwarded-Encrypted: i=1; AJvYcCX1plZSiKkjaPUFqWLsvS73GaMIspe/wQYb+dU4eXoieGAmXkw7OMkojMMrLX7fDabMIrhnByhxEL7gldU=@vger.kernel.org X-Gm-Message-State: AOJu0YwLVUkXVWfN28njN0GQEZIp5Hin2lUUzJvapoLHStHXcwUigjTy ST555NZdFQwVHqCpQKqxNLcSgkd1KlaqzuTOU9OEm4TAFKTDw2jRkJWBCd9i4uZLnc20AnLG62V JjCxqDf2kp+u+SVL/vTezhiqSz0tOUTB+6OilWKv2d7ioONt5k4g3dFzDwmsI7HapZoc4Vo78VQ 1zsBmvx4tqZJDP462l3NmcE2WMtCMb2gWw+7OTnfMN02kkTWV4A0oP5Hprzwp9eSM4mQwlbYF9r U/uHi4v2Lf6AUyo X-Gm-Gg: ATEYQzxGi34xvoZSi9BMIDIEYLJ6HftmTrEhffj0/wd9uWY4cuDj/E1F7H8Q0fEauCK KTVLp6a/IF/PYcISJqRd8SMP/MA9z2nV3i7AJoj8sHYMlTc4pORu3VRhTMzl8vnLIzbNSziLSGj 7bFHdY7wu0QQrrfjjJPjygXXXGGmcpk+gCCjlCk+e7a8Mmkpiqw/Na8D22hVG80eix0IRZQT+I0 5gCS8EkIXVBGsKyh56pWJYtBoTs7MPkNe/7xyV9PRvRdQKpA5O4eRyeYOwuhKKRMK2d/sFGi7zv rlaYi+m95F4ySWi/wD4VArCGvmw3OcXgpsfgNaGBZa5b+CCiW0Qz/f6nLDW2vNgRRJQ8WPPkABF BnbN6EFp092afH+RmcP1ZTyJ160vyptwDLvUXrNpEPSUGRuWIiPtrTJ+F1H3UNCOfVLTT6neNOP vX+6c4oW6fxKjtui3RpOzTxopVx3bUWA3weHvIxwfltCqLKm9l2GXBuKoW X-Received: by 2002:a05:6870:a454:b0:417:4320:26fd with SMTP id 586e51a60fabf-41c10e86b4cmr2804856fac.7.1774038828222; Fri, 20 Mar 2026 13:33:48 -0700 (PDT) Received: from smtp-us-east1-p01-i01-si01.dlp.protect.broadcom.com (address-144-49-247-102.dlp.protect.broadcom.com. [144.49.247.102]) by smtp-relay.gmail.com with ESMTPS id 586e51a60fabf-41c14e0a584sm462189fac.13.2026.03.20.13.33.47 for (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Fri, 20 Mar 2026 13:33:48 -0700 (PDT) X-Relaying-Domain: broadcom.com X-CFilter-Loop: Reflected Received: by mail-qk1-f200.google.com with SMTP id af79cd13be357-8cd80bea54dso1388980885a.3 for ; Fri, 20 Mar 2026 13:33:47 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=broadcom.com; s=google; t=1774038827; x=1774643627; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=1CXtTypYyHqjl2V69Ghw/md4zg3U7POaN7Ulg0LlUBU=; b=faaQK0pwL820M7tRl1pQ09cXnew/+OTBafmmefd1j6ti/iyi4/TZzdVRmZEx6eWKVQ vAW9uxNzcm3sk7+wJAKY0FtxIr6G1MQkooCM5YidxHl3Am/pZ5mN644IARWz7HEMtwUX Al0unux6zLlVSE+A4ez6/Hy72xfuZWak108qY= X-Forwarded-Encrypted: i=1; AJvYcCVH4KBneuTe+prY0WNU5ZLgshYiaf8kQxgAs2w+kqMqv78dD+WL7AoqU+QgSGD2WxoskgfmXZI2t/c/g4s=@vger.kernel.org X-Received: by 2002:a05:620a:4689:b0:8cd:af31:b416 with SMTP id af79cd13be357-8cfc7f4b0a7mr653888685a.34.1774038827023; Fri, 20 Mar 2026 13:33:47 -0700 (PDT) X-Received: by 2002:a05:620a:4689:b0:8cd:af31:b416 with SMTP id af79cd13be357-8cfc7f4b0a7mr653880885a.34.1774038826140; Fri, 20 Mar 2026 13:33:46 -0700 (PDT) Received: from mail.broadcom.net ([192.19.144.250]) by smtp.gmail.com with ESMTPSA id af79cd13be357-8cfc8fb9c10sm280699985a.19.2026.03.20.13.33.45 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 20 Mar 2026 13:33:45 -0700 (PDT) From: Kamal Dasu To: Ulf Hansson , Adrian Hunter , Kees Cook Cc: Tony Luck , "Guilherme G . Piccoli" , Florian Fainelli , Arend van Spriel , William Zhang , bcm-kernel-feedback-list@broadcom.com, linux-mmc@vger.kernel.org, linux-kernel@vger.kernel.org, Kamal Dasu Subject: [PATCH v4 2/4] mmc: sdhci: Implement panic-context write support Date: Fri, 20 Mar 2026 16:32:47 -0400 Message-Id: <20260320203249.1965044-3-kamal.dasu@broadcom.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260320203249.1965044-1-kamal.dasu@broadcom.com> References: <20260320203249.1965044-1-kamal.dasu@broadcom.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-DetectorID-Processed: b00c1d49-9d2e-4205-b15f-d015386d3d5e Content-Type: text/plain; charset="utf-8" Implement the panic-context host operations for SDHCI controllers: sdhci_panic_prepare(): Reset the controller, drain any pending requests by polling Present State, and clear interrupt status to start from a known-good state. sdhci_panic_poll_completion(): Poll for command and data completion using register reads instead of waiting for interrupts. sdhci_panic_complete(): Clear interrupt status and restore the host to normal operation after panic I/O. Make sdhci_send_command_retry() panic-safe by using mdelay() instead of usleep_range() when oops_in_progress is set, and suppress WARN output during panic. Add oops_in_progress guards to sdhci_timeout_timer() and sdhci_timeout_data_timer() to prevent spurious timeout handling during panic writes. Signed-off-by: Kamal Dasu --- drivers/mmc/host/sdhci.c | 169 ++++++++++++++++++++++++++++++++++++++- drivers/mmc/host/sdhci.h | 6 ++ 2 files changed, 171 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index fec9329e1edb..c40084e07eca 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -1765,17 +1766,22 @@ static bool sdhci_send_command_retry(struct sdhci_h= ost *host, =20 while (!sdhci_send_command(host, cmd)) { if (!timeout--) { - pr_err("%s: Controller never released inhibit bit(s).\n", - mmc_hostname(host->mmc)); + if (!oops_in_progress) { + pr_err("%s: Controller never released inhibit bit(s).\n", + mmc_hostname(host->mmc)); + sdhci_dumpregs(host); + } sdhci_err_stats_inc(host, CTRL_TIMEOUT); - sdhci_dumpregs(host); cmd->error =3D -EIO; return false; } =20 spin_unlock_irqrestore(&host->lock, flags); =20 - usleep_range(1000, 1250); + if (unlikely(oops_in_progress)) + mdelay(1); + else + usleep_range(1000, 1250); =20 present =3D host->mmc->ops->get_cd(host->mmc); =20 @@ -3076,6 +3082,152 @@ static void sdhci_card_event(struct mmc_host *mmc) spin_unlock_irqrestore(&host->lock, flags); } =20 +/* + * Panic-context operations for pstore backends. + * These run with interrupts disabled and other CPUs stopped. + */ + +#define SDHCI_PANIC_POLL_ITERATIONS 2000 +#define SDHCI_PANIC_POLL_DELAY_US 500 +#define SDHCI_PANIC_MIN_POLL_COUNT 300 +#define SDHCI_PANIC_RESET_TIMEOUT_US 100000 +#define SDHCI_PANIC_DRAIN_TIMEOUT_US 100000 + +/** + * sdhci_panic_prepare - Prepare SDHCI controller for panic-context I/O + * @mmc: MMC host structure + * + * Called during kernel panic. Drains any in-flight request, resets the + * CMD and DATA lines, then clears software state under spinlock. + * The drain + reset ensures no stopped CPU is still inside sdhci_irq + * holding host->lock by the time we take it. + */ +int sdhci_panic_prepare(struct mmc_host *mmc) +{ + struct sdhci_host *host =3D mmc_priv(mmc); + unsigned long flags; + u32 present; + u8 val; + int ret; + + /* + * If the controller has a request in flight, give it a short + * bounded time to finish. The CMD/DATA reset below will + * force-abort anything that doesn't complete in time. + */ + present =3D sdhci_readl(host, SDHCI_PRESENT_STATE); + if (present & (SDHCI_CMD_INHIBIT | SDHCI_DATA_INHIBIT)) { + readl_poll_timeout_atomic(host->ioaddr + SDHCI_PRESENT_STATE, + present, + !(present & (SDHCI_CMD_INHIBIT | + SDHCI_DATA_INHIBIT)), + 10, SDHCI_PANIC_DRAIN_TIMEOUT_US); + } + + sdhci_writeb(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA, + SDHCI_SOFTWARE_RESET); + + ret =3D readb_poll_timeout_atomic(host->ioaddr + SDHCI_SOFTWARE_RESET, + val, !(val & (SDHCI_RESET_CMD | SDHCI_RESET_DATA)), + 10, SDHCI_PANIC_RESET_TIMEOUT_US); + + spin_lock_irqsave(&host->lock, flags); + host->cmd =3D NULL; + host->data =3D NULL; + host->data_cmd =3D NULL; + host->mrqs_done[0] =3D NULL; + host->mrqs_done[1] =3D NULL; + spin_unlock_irqrestore(&host->lock, flags); + + if (host->ops && host->ops->panic_prepare) + host->ops->panic_prepare(host); + + return ret; +} +EXPORT_SYMBOL_GPL(sdhci_panic_prepare); + +/** + * sdhci_panic_poll_completion - Poll SDHCI registers for request completi= on + * @mmc: MMC host structure + * @mrq: MMC request being polled for completion + * + * Checks interrupt status and present state registers to determine if a + * request has completed. Used during panic when interrupts are disabled. + */ +bool sdhci_panic_poll_completion(struct mmc_host *mmc, struct mmc_request = *mrq) +{ + struct sdhci_host *host =3D mmc_priv(mmc); + unsigned int poll_count; + u32 int_status, present; + + for (poll_count =3D 0; poll_count < SDHCI_PANIC_POLL_ITERATIONS; + poll_count++) { + cpu_relax(); + udelay(SDHCI_PANIC_POLL_DELAY_US); + + int_status =3D sdhci_readl(host, SDHCI_INT_STATUS); + + if (int_status & SDHCI_INT_ERROR) { + if (mrq->cmd) + mrq->cmd->error =3D -EIO; + if (mrq->data) + mrq->data->error =3D -EIO; + sdhci_writel(host, int_status, SDHCI_INT_STATUS); + return true; + } + + if (int_status & SDHCI_INT_RESPONSE) + sdhci_writel(host, SDHCI_INT_RESPONSE, + SDHCI_INT_STATUS); + + if (int_status & SDHCI_INT_DATA_END) + sdhci_writel(host, SDHCI_INT_DATA_END, + SDHCI_INT_STATUS); + + /* + * Use the same completion heuristic as the working v5 + * implementation: after a minimum number of poll + * iterations, treat the request as complete when the + * DATA_INHIBIT bit clears (controller is idle). + */ + if (poll_count >=3D SDHCI_PANIC_MIN_POLL_COUNT) { + present =3D sdhci_readl(host, SDHCI_PRESENT_STATE); + if (!(present & SDHCI_DATA_INHIBIT)) + return true; + } + } + + if (mrq->cmd) + mrq->cmd->error =3D -ETIMEDOUT; + if (mrq->data) + mrq->data->error =3D -ETIMEDOUT; + return false; +} +EXPORT_SYMBOL_GPL(sdhci_panic_poll_completion); + +/** + * sdhci_panic_complete - Clean up SDHCI state after a panic-context reque= st + * @mmc: MMC host structure + * @mrq: MMC request that has completed + * + * Clears host software state under spinlock so the next panic request + * starts clean. + */ +void sdhci_panic_complete(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct sdhci_host *host =3D mmc_priv(mmc); + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + host->cmd =3D NULL; + host->data =3D NULL; + host->data_cmd =3D NULL; + host->mrqs_done[0] =3D NULL; + host->mrqs_done[1] =3D NULL; + spin_unlock_irqrestore(&host->lock, flags); +} +EXPORT_SYMBOL_GPL(sdhci_panic_complete); + static const struct mmc_host_ops sdhci_ops =3D { .request =3D sdhci_request, .post_req =3D sdhci_post_req, @@ -3091,6 +3243,9 @@ static const struct mmc_host_ops sdhci_ops =3D { .execute_tuning =3D sdhci_execute_tuning, .card_event =3D sdhci_card_event, .card_busy =3D sdhci_card_busy, + .panic_prepare =3D sdhci_panic_prepare, + .panic_poll_completion =3D sdhci_panic_poll_completion, + .panic_complete =3D sdhci_panic_complete, }; =20 /*************************************************************************= ****\ @@ -3242,6 +3397,9 @@ static void sdhci_timeout_timer(struct timer_list *t) =20 host =3D timer_container_of(host, t, timer); =20 + if (oops_in_progress) + return; + spin_lock_irqsave(&host->lock, flags); =20 if (host->cmd && !sdhci_data_line_cmd(host->cmd)) { @@ -3264,6 +3422,9 @@ static void sdhci_timeout_data_timer(struct timer_lis= t *t) =20 host =3D timer_container_of(host, t, data_timer); =20 + if (oops_in_progress) + return; + spin_lock_irqsave(&host->lock, flags); =20 if (host->data || host->data_cmd || diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index b6a571d866fa..396eca56439f 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -724,6 +724,7 @@ struct sdhci_ops { void (*dump_vendor_regs)(struct sdhci_host *host); void (*dump_uhs2_regs)(struct sdhci_host *host); void (*uhs2_pre_detect_init)(struct sdhci_host *host); + void (*panic_prepare)(struct sdhci_host *host); }; =20 #ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS @@ -906,6 +907,11 @@ void sdhci_switch_external_dma(struct sdhci_host *host= , bool en); void sdhci_set_data_timeout_irq(struct sdhci_host *host, bool enable); void __sdhci_set_timeout(struct sdhci_host *host, struct mmc_command *cmd); =20 +int sdhci_panic_prepare(struct mmc_host *mmc); +bool sdhci_panic_poll_completion(struct mmc_host *mmc, + struct mmc_request *mrq); +void sdhci_panic_complete(struct mmc_host *mmc, struct mmc_request *mrq); + #if defined(CONFIG_DYNAMIC_DEBUG) || \ (defined(CONFIG_DYNAMIC_DEBUG_CORE) && defined(DYNAMIC_DEBUG_MODULE)) #define SDHCI_DBG_ANYWAY 0 --=20 2.34.1 From nobody Sat Apr 4 03:19:57 2026 Received: from mail-dy1-f226.google.com (mail-dy1-f226.google.com [74.125.82.226]) (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 666D135A3A2 for ; Fri, 20 Mar 2026 20:33:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.226 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774038838; cv=none; b=D1GMkKfHhQwVrZYkzOQNV7VFF7++0Snm3wYbNo9aoSrj/euGilKvkcP9wcbqaaeA9K4LVW7Hj/Ylq0ejDmUFNBVsoZNXdfGUdachLzJl/ggBZkYqw1ohSIXM1efawXBjDv8HztK9mcyzwwicEEnpv9OvDAkB0LbMbd0TTyyYZD4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774038838; c=relaxed/simple; bh=7AmnG/UUYqW9PbgLMpQCGJh+iAZg56GN9U6BhFeGBjM=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=BQRizPCC5mzwUP3FtO/wFwicGxoG4tnpfmjph43bX9xKINOFFUZToKXhMudUqkbGiyY/w0fjPh67b32XXxisTdPcGxa/cUTLjmmaKZEpckHwdBo58CfdWooEoEd4HKz4p4f7jns2NXzuK+STqgEU6aoBx+biyG6fbeQ62/DwVS0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=broadcom.com; spf=fail smtp.mailfrom=broadcom.com; dkim=pass (1024-bit key) header.d=broadcom.com header.i=@broadcom.com header.b=JahlS7dQ; arc=none smtp.client-ip=74.125.82.226 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=broadcom.com Authentication-Results: smtp.subspace.kernel.org; spf=fail smtp.mailfrom=broadcom.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=broadcom.com header.i=@broadcom.com header.b="JahlS7dQ" Received: by mail-dy1-f226.google.com with SMTP id 5a478bee46e88-2b6b0500e06so2665377eec.1 for ; Fri, 20 Mar 2026 13:33:56 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774038835; x=1774643635; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:dkim-signature:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=2eHxZs/jSCBES4akkCBqfFi+qs+gICn2xClTm9J1Alg=; b=MkBASlCtUHVuRjbeECv9DUMjczs/WDVJZXrFW8rCZLft/6HOTg8jdtgiT1IOJTqnJ1 Pbl24TjTcJVz3ISy9Do6Pwhle8umqTJM3r71/entEWWhmWEUv19Ho4yUTLJ0cHHr038K Cwzh3hEIp1gu55VLJVAZe3Sbpy1FGNzoZrcBneEfz0ufMuEQr3XGKab1ePtSEePevvxS Q4C5aFg6+FeDBul6HRkhzbKE5mly1VKRH61dY9GFzHWv+ipKHSQBbaqKMFpfz4dNQPzJ /J2dmwcO57nGiO9tA38Qv/srmuMGribjgE7B/Ha6F7b+RtjzQecKWrjjRH2JStEQNuZ/ YuYQ== X-Forwarded-Encrypted: i=1; AJvYcCXE7vjeKlqMSFyLrgJMBlWW6gvO9T6rrFpKWELvDEz0vmRDT5TkIUUxmMK8v8BZkLiGqbX0tCBLJcuahfc=@vger.kernel.org X-Gm-Message-State: AOJu0YwbuxtM2hC3QFFQaN+MvHiMM3QhpK5bkjFhpJ1AireBSo3DJmuo rk18qw5AIMbkXAxf2i9aqrhIpuQcBx9xUBOF0ToVeaceGgNotXADRt4gZOkCL2bP5FPlKGG6qUJ Ko3CQPVzZwKTgaIQ+u/eoOqRz0noRbHUaIOEVbjQkcOS3n2glSqhs9p6CrMn25XQAlAvMed5rLc fCAvmpDKs9xxA8E2SJn2AgdOImKrm6PTGIueP+3sM+E14CVj2ewO/Lex8UH5a3NwglyDkNtnxYz lztAsrfLbdgiLVE X-Gm-Gg: ATEYQzynH8NqibLDBKWOn7/qfqQBy4lFRzRHVDulIw/A+Cd4Q7B/HPAUjQjnG7JFnxy XWJHOr8dRbsje9JB1bh0U2fBNMvfBkGd/vWeVEG3gsHOpXHrtR1XgeOMQHnPtGZwXjY8F+k9png QdWgcz6pCztH1OGkYssdeVKghV6gB6zHiRKnW9VWg1zHNPYCCYaBAT+BD1OhYyID4PMjKpXigUc DJSHKXr0MR+JHwdDgD3WrdBBWsmeU6TEpLYIamSGPUBMLVcfBF/NDecJcrZSTMHvdJGuhaZUMJr TW4r2Tkrh0sygp0W4OU9eCVGQ73CFbtyB8kpNZcvTVF50wVBox11T+FoxYQD48gsP5ws0Wt9kb+ n+xarge5U/OfgUmLC9EUesA0y+6CpmLFl5+6AL7UjszJ19F7ILgQiEKj3Wo0Sz7Qc3gcy8L49DA UAwrukQ2VPKT8HlINga0UYUW6NXzG8xIUuOwQlXRNecNa4IxSuYGTfv+VS X-Received: by 2002:a05:693c:2c0f:b0:2b8:26b8:3446 with SMTP id 5a478bee46e88-2c1095a75e9mr1826562eec.2.1774038835269; Fri, 20 Mar 2026 13:33:55 -0700 (PDT) Received: from smtp-us-east1-p01-i01-si01.dlp.protect.broadcom.com (address-144-49-247-117.dlp.protect.broadcom.com. [144.49.247.117]) by smtp-relay.gmail.com with ESMTPS id 5a478bee46e88-2c10b162b62sm282324eec.4.2026.03.20.13.33.55 for (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Fri, 20 Mar 2026 13:33:55 -0700 (PDT) X-Relaying-Domain: broadcom.com X-CFilter-Loop: Reflected Received: by mail-qk1-f197.google.com with SMTP id af79cd13be357-8cd7f6ac239so1386508485a.2 for ; Fri, 20 Mar 2026 13:33:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=broadcom.com; s=google; t=1774038834; x=1774643634; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=2eHxZs/jSCBES4akkCBqfFi+qs+gICn2xClTm9J1Alg=; b=JahlS7dQmL4FYrMG7cgqa8qH81d8VeQRG6ptcM6Cu6wKLLf7zECqyTNxuQWSZfacAp 3tUGx9BnwWAdGz3VUloJwPz1pWv0LiK8FGcJEpE5VhOftqYurnHkE1mU7RZrYrkd9u/B 4ZhOeq5ybz4LXtRtI15K13mif/wH1C69z4yXI= X-Forwarded-Encrypted: i=1; AJvYcCWY2TX0ce+aSdHMk7+1B1uX72KIQNohpUSl6eZ4t/r6GppWRAw6bANGjN+35G2hm2aUomXeGzxNO/E9uAI=@vger.kernel.org X-Received: by 2002:a05:620a:4487:b0:8cd:97de:bb52 with SMTP id af79cd13be357-8cfc7e71174mr640561185a.22.1774038828694; Fri, 20 Mar 2026 13:33:48 -0700 (PDT) X-Received: by 2002:a05:620a:4487:b0:8cd:97de:bb52 with SMTP id af79cd13be357-8cfc7e71174mr640556185a.22.1774038828133; Fri, 20 Mar 2026 13:33:48 -0700 (PDT) Received: from mail.broadcom.net ([192.19.144.250]) by smtp.gmail.com with ESMTPSA id af79cd13be357-8cfc8fb9c10sm280699985a.19.2026.03.20.13.33.47 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 20 Mar 2026 13:33:47 -0700 (PDT) From: Kamal Dasu To: Ulf Hansson , Adrian Hunter , Kees Cook Cc: Tony Luck , "Guilherme G . Piccoli" , Florian Fainelli , Arend van Spriel , William Zhang , bcm-kernel-feedback-list@broadcom.com, linux-mmc@vger.kernel.org, linux-kernel@vger.kernel.org, Kamal Dasu Subject: [PATCH v4 3/4] mmc: block: Add helper to look up mmc_card by device name Date: Fri, 20 Mar 2026 16:32:48 -0400 Message-Id: <20260320203249.1965044-4-kamal.dasu@broadcom.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260320203249.1965044-1-kamal.dasu@broadcom.com> References: <20260320203249.1965044-1-kamal.dasu@broadcom.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-DetectorID-Processed: b00c1d49-9d2e-4205-b15f-d015386d3d5e Content-Type: text/plain; charset="utf-8" Add mmc_blk_get_card_by_name() which resolves an MMC block device name (e.g., "mmcblk1") to its associated struct mmc_card by opening the block device and traversing gendisk to the mmc_blk_data private data. This is needed by the mmcpstore backend driver to find the card associated with a given pstore_blk block device path. When mmcpstore is built as a module, the eMMC card is already probed by the time the module loads, so the helper can look it up directly. Signed-off-by: Kamal Dasu --- drivers/mmc/core/block.c | 54 ++++++++++++++++++++++++++++++++++++++++ drivers/mmc/core/block.h | 4 +++ 2 files changed, 58 insertions(+) diff --git a/drivers/mmc/core/block.c b/drivers/mmc/core/block.c index 05ee76cb0a08..91e3a778d5b2 100644 --- a/drivers/mmc/core/block.c +++ b/drivers/mmc/core/block.c @@ -3441,6 +3441,60 @@ static void __exit mmc_blk_exit(void) bus_unregister(&mmc_rpmb_bus_type); } =20 +/** + * mmc_blk_get_card_by_name - Get mmc_card from device name + * @device_name: Name of the MMC device (e.g., "mmcblk1") + * + * Resolves an MMC block device name to its associated struct mmc_card. + * Used by the mmcpstore backend driver (when built as a module) to find + * the card associated with a given pstore_blk block device path. + * + * Returns: mmc_card pointer on success, NULL on failure + */ +struct mmc_card *mmc_blk_get_card_by_name(const char *device_name) +{ + struct file *bdev_file; + struct block_device *bdev; + struct gendisk *disk; + struct mmc_blk_data *md; + struct mmc_card *card =3D NULL; + char dev_path[32]; + + if (!device_name) + return NULL; + + snprintf(dev_path, sizeof(dev_path), "/dev/%s", device_name); + + bdev_file =3D bdev_file_open_by_path(dev_path, BLK_OPEN_READ, NULL, NULL); + if (IS_ERR(bdev_file)) { + pr_debug("mmc_blk: Failed to open block device %s: %ld\n", + dev_path, PTR_ERR(bdev_file)); + return NULL; + } + bdev =3D file_bdev(bdev_file); + + disk =3D bdev->bd_disk; + if (!disk) { + pr_err("mmc_blk: No gendisk found for %s\n", dev_path); + goto out_put_bdev; + } + + md =3D disk->private_data; + if (!md) { + pr_debug("mmc_blk: No mmc_blk_data found for %s\n", dev_path); + goto out_put_bdev; + } + + card =3D md->queue.card; + if (!card) + pr_err("mmc_blk: No mmc_card found for %s\n", dev_path); + +out_put_bdev: + fput(bdev_file); + return card; +} +EXPORT_SYMBOL_GPL(mmc_blk_get_card_by_name); + module_init(mmc_blk_init); module_exit(mmc_blk_exit); =20 diff --git a/drivers/mmc/core/block.h b/drivers/mmc/core/block.h index 31153f656f41..08798c56dcce 100644 --- a/drivers/mmc/core/block.h +++ b/drivers/mmc/core/block.h @@ -17,4 +17,8 @@ struct work_struct; =20 void mmc_blk_mq_complete_work(struct work_struct *work); =20 +/* MMC block device helper for mmcpstore */ +struct mmc_card; +struct mmc_card *mmc_blk_get_card_by_name(const char *device_name); + #endif --=20 2.34.1 From nobody Sat Apr 4 03:19:57 2026 Received: from mail-pf1-f225.google.com (mail-pf1-f225.google.com [209.85.210.225]) (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 B5D8434F486 for ; Fri, 20 Mar 2026 20:33:55 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.225 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774038839; cv=none; b=Fonw1uu6O7wdA83Ph2WAUgefjwsfcaVUmvpTR8VFTxhis9gSVTbaV5lLpDdMCJWgezV22OANbvJHdHgWPNIXOV4DYfVe+1G8P7GS96D9bwx1jbR67MoM2os6DaRIqtr4T0LaYmueOFNXSY+rOjnYMgAiFqwIHNUjOy3DgCRKw8g= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774038839; c=relaxed/simple; bh=ttForu8euJgwcpReJfpmXZkLpaVvMA49P2Y4qNLAQjw=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=D+CsSVoZo4H8Zkf4fBXFXicAPbfD2CyjWqS/Qx9ypYH9SKCZoti0sh6ma1wCKj5TYfsi4yIy9+L6CMNWGd0SOw7TAaIn/qnpN8pTuxK6yxd2VCsmKTNCkJmNxFx4j6gA4cqp42mmYFfvmMGUDEmNAU7EN5R84JlVWvRDesuYtAY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=broadcom.com; spf=fail smtp.mailfrom=broadcom.com; dkim=pass (1024-bit key) header.d=broadcom.com header.i=@broadcom.com header.b=GHFQybFe; arc=none smtp.client-ip=209.85.210.225 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=broadcom.com Authentication-Results: smtp.subspace.kernel.org; spf=fail smtp.mailfrom=broadcom.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=broadcom.com header.i=@broadcom.com header.b="GHFQybFe" Received: by mail-pf1-f225.google.com with SMTP id d2e1a72fcca58-8296dabef74so1813416b3a.1 for ; Fri, 20 Mar 2026 13:33:55 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774038835; x=1774643635; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:dkim-signature:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=gVzv1TLkyid8aOTTqq1Wj5xF6RqK8cnizQ4h9a04vbQ=; b=cktrIQPTZvqfqMU28H1yeI4lfZNeeyERFBPZtTx8s10st6kF7BUeKFC3S1Z+GBxsoR ZJvPxHTD+WQrNBV5fczBw8NagetZKA6MEeYq/MlUctOKmUkA31g6TR2mee8uJlxlo+hG YJ4wQNT1Bjg5qr8oGmMD/eaq2DHYYpsnjYoCtG6LGOVNplGQuLuv40BGs2xnY3380jKn 9+UiVhC0r6KJyk85TbrXe7fTJwndO7nU52X0eKYd99AfpYusAudFI4zI3PnRGIkHVFmO O2Y4TQz/YH9/JhQGV7veE3WWWuNOr1tD++NKG+4e8pLLJBaYN+DAnXwYnM3ftBAXLdEE XGaA== X-Forwarded-Encrypted: i=1; AJvYcCXC6TIWitHxTer+NoHyaX4vY5X5TR+kTkgZqhUWA1kdUbWOcc7L3UfJw74M1smy+mvHyWso7yGtfuNkT2U=@vger.kernel.org X-Gm-Message-State: AOJu0YxVMw3TKD9AK4VrVdDPfHxJwprmZAIfYUFVkbGLInvK2DS5MgGP FKjd0ARRGRtqmzcVQPll4RSCjv7Zwbb85j43eBhQXWYYCTSUXn9QFmRLgrEqdjnzu+25VrpANKf QqD3g83Oz6HLkN8PJyRD3CS4TJ7gXOz2P7Y/6rSwctcrbw1Ebt+EQtX3goWi5DMgDnwT1jtJ/tR UbCrwWBV+pOEpdKf4ufDoleM8ZlEcHBno+CSmnXD9LqAVYHJ6mat186d5NPiGO9hd8lZMtHwS49 Cwq3ou7Ze1fHK2T X-Gm-Gg: ATEYQzy+CbFXsNSPRag+qt+k9uRu3dCjEZ6RY4Zb+fSzjwDyJi2xKLJLyd7buca3qxx iQeMiNluKrNwUU/fYT53WafIhf1qdNmMyfjRxBiBIC+Ra/WOMMhe5wHAjLsEauM7nbHriJlQZuZ f047bHPcsnvy4HHvrmZEarYkftIkKzDz6LhIljfhr4QlZe4PB+J3F3tpC5lKTsgshyS2yE9mZ87 8HysIrrYxFkVAUX54sNSFpXdEeB/epgAHhRSVqdGt9vdEHz00WUc+hJQ2c1lkKpACZwmxc2pgBL BoeSyZRnzN3zz4e7u5AYB+VgnBbznKiWjU/h0P3Evr7uQjtR2ARzZ/09EMd7Cra5sknUk3T4mUv upE9X4MB+5AfJkKZtTvwde2nF6q2RKxjHivIVTn4uqcpdrTbAcJmdwGsbeec3M9G0W1/0E2pzOz G7MKCkpZg8T9CSKhQKetPe8ZN95WwVKkhMyJ4DxELqk452WvxpVnMTGQ== X-Received: by 2002:a05:6a00:99c:b0:827:3ed6:9122 with SMTP id d2e1a72fcca58-82a8c36f76dmr4053847b3a.59.1774038834981; Fri, 20 Mar 2026 13:33:54 -0700 (PDT) Received: from smtp-us-east1-p01-i01-si01.dlp.protect.broadcom.com (address-144-49-247-20.dlp.protect.broadcom.com. [144.49.247.20]) by smtp-relay.gmail.com with ESMTPS id d2e1a72fcca58-82b03f63633sm334901b3a.4.2026.03.20.13.33.54 for (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Fri, 20 Mar 2026 13:33:54 -0700 (PDT) X-Relaying-Domain: broadcom.com X-CFilter-Loop: Reflected Received: by mail-qk1-f200.google.com with SMTP id af79cd13be357-8cd7c4ab845so902634285a.1 for ; Fri, 20 Mar 2026 13:33:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=broadcom.com; s=google; t=1774038834; x=1774643634; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=gVzv1TLkyid8aOTTqq1Wj5xF6RqK8cnizQ4h9a04vbQ=; b=GHFQybFeZTjaK20PbtyifKTQArJD4GbLWlSVOsJhAk1NSJpSnnBO4IdvwNjQ9L6khy SGJKmYsHhHo7ACZnYlObqvvCZ6nqjLmACGtxus1NOonab/Rkhy76xmkIH2WYTVCk4hK7 pXMTUojyiLZQ2BBPDA2rP+SZJmSpe4i/9/kUA= X-Forwarded-Encrypted: i=1; AJvYcCUKmD2uixtTGxQ49PRzk40317w+98tEjc3CsR8b4EBnYxngqhDui8xMDVnGJCMfTyckxEdQdr7HEHnBPrM=@vger.kernel.org X-Received: by 2002:a05:620a:294e:b0:8cf:be85:ba84 with SMTP id af79cd13be357-8cfc7f67926mr595661985a.54.1774038832540; Fri, 20 Mar 2026 13:33:52 -0700 (PDT) X-Received: by 2002:a05:620a:294e:b0:8cf:be85:ba84 with SMTP id af79cd13be357-8cfc7f67926mr595654985a.54.1774038831492; Fri, 20 Mar 2026 13:33:51 -0700 (PDT) Received: from mail.broadcom.net ([192.19.144.250]) by smtp.gmail.com with ESMTPSA id af79cd13be357-8cfc8fb9c10sm280699985a.19.2026.03.20.13.33.50 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 20 Mar 2026 13:33:51 -0700 (PDT) From: Kamal Dasu To: Ulf Hansson , Adrian Hunter , Kees Cook Cc: Tony Luck , "Guilherme G . Piccoli" , Florian Fainelli , Arend van Spriel , William Zhang , bcm-kernel-feedback-list@broadcom.com, linux-mmc@vger.kernel.org, linux-kernel@vger.kernel.org, Kamal Dasu Subject: [PATCH v4 4/4] mmc: core: Add MMC pstore backend driver Date: Fri, 20 Mar 2026 16:32:49 -0400 Message-Id: <20260320203249.1965044-5-kamal.dasu@broadcom.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260320203249.1965044-1-kamal.dasu@broadcom.com> References: <20260320203249.1965044-1-kamal.dasu@broadcom.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-DetectorID-Processed: b00c1d49-9d2e-4205-b15f-d015386d3d5e Content-Type: text/plain; charset="utf-8" Add mmcpstore, a pstore backend driver that provides persistent storage for kernel crash logs and other pstore data on eMMC devices. The driver registers with the pstore_blk framework and performs direct MMC writes during kernel panic using the panic-context host operations. Key features: - Panic-safe writes using polled I/O with interrupts disabled - Sector offset and count parameters for flexible partition usage - Support for both module and builtin configurations - PM suspend/resume support with eMMC hardware re-initialization - Dual-path registration: direct probe callback when builtin, mmc_blk_get_card_by_name() lookup when loaded as module - Console pstore support via atomic context detection: when called from printk's atomic console path, returns -EBUSY so pstore_zone defers the write to its workqueue. Console logging works but is not recommended when kmsg is enabled, as it generates constant flash writes for every kernel log message. When CONFIG_MMC_PSTORE=3Dy, mmc_blk_probe() calls mmcpstore_card_add() directly. When CONFIG_MMC_PSTORE=3Dm, the module init uses mmc_blk_get_card_by_name() to look up the already-probed eMMC card. The IS_BUILTIN() guard in block.h ensures no cross-module symbol dependency. Required kernel config: CONFIG_PSTORE=3Dy CONFIG_PSTORE_BLK=3Dm (or =3Dy) CONFIG_MMC_PSTORE=3Dm (or =3Dy) CONFIG_PSTORE_PMSG=3Dy (optional, for pmsg support) CONFIG_PSTORE_COMPRESS=3Dy (optional, for compressed kmsg dumps) CONFIG_PSTORE_CONSOLE=3Dy (optional, console log capture) Module loading examples: Basic usage with a dedicated eMMC partition: modprobe pstore_blk blkdev=3D/dev/mmcblk1p5 kmsg_size=3D64 pmsg_size=3D= 32 modprobe mmcpstore With console log capture: modprobe pstore_blk blkdev=3D/dev/mmcblk1p5 kmsg_size=3D64 pmsg_size=3D= 32 \ console_size=3D32 modprobe mmcpstore Using a subset of sectors within a partition: modprobe pstore_blk blkdev=3D/dev/mmcblk1p5 kmsg_size=3D64 pmsg_size=3D= 32 modprobe mmcpstore sector_offset=3D512 sector_count=3D512 Builtin configuration (kernel command line): pstore_blk.blkdev=3D/dev/mmcblk1p5 pstore_blk.kmsg_size=3D64 \ pstore_blk.pmsg_size=3D32 Verify stored pstore data after a panic reboot: mount -t pstore pstore /sys/fs/pstore ls /sys/fs/pstore/ cat /sys/fs/pstore/dmesg-pstore_blk-0 cat /sys/fs/pstore/pmsg-pstore_blk-0 Module parameters: sector_offset Starting sector within partition (default: 0) sector_count Number of sectors to use (default: entire partition) Tested with ARM and ARM64 builds on Broadcom STB platforms with SDHCI controllers. Signed-off-by: Kamal Dasu --- MAINTAINERS | 6 + drivers/mmc/core/Kconfig | 12 + drivers/mmc/core/Makefile | 1 + drivers/mmc/core/block.c | 2 + drivers/mmc/core/block.h | 14 + drivers/mmc/core/mmcpstore.c | 1519 ++++++++++++++++++++++++++++++++++ 6 files changed, 1554 insertions(+) create mode 100644 drivers/mmc/core/mmcpstore.c diff --git a/MAINTAINERS b/MAINTAINERS index 7d10988cbc62..29c334c5f9d5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18089,6 +18089,12 @@ F: drivers/mmc/ F: include/linux/mmc/ F: include/uapi/linux/mmc/ =20 +MULTIMEDIA CARD (MMC) PSTORE BACKEND +M: Kamal Dasu +L: linux-mmc@vger.kernel.org +S: Maintained +F: drivers/mmc/core/mmcpstore.c + MULTIPLEXER SUBSYSTEM M: Peter Rosin S: Odd Fixes diff --git a/drivers/mmc/core/Kconfig b/drivers/mmc/core/Kconfig index 14d2ecbb04d3..961c76f4b6c7 100644 --- a/drivers/mmc/core/Kconfig +++ b/drivers/mmc/core/Kconfig @@ -23,6 +23,18 @@ config PWRSEQ_SD8787 This driver can also be built as a module. If so, the module will be called pwrseq_sd8787. =20 +config MMC_PSTORE + tristate "MMC pstore backend support" + depends on MMC_BLOCK + select PSTORE_BLK + help + This option enables MMC devices to be used as pstore backends. + This allows kernel crash logs and other pstore data to be stored + on MMC/SD storage devices for post-mortem analysis. + + If you want to store kernel crash logs on MMC devices, say Y. + + config PWRSEQ_SIMPLE tristate "Simple HW reset support for MMC" default y diff --git a/drivers/mmc/core/Makefile b/drivers/mmc/core/Makefile index 15b067e8b0d1..6b7bbfd2adbe 100644 --- a/drivers/mmc/core/Makefile +++ b/drivers/mmc/core/Makefile @@ -19,3 +19,4 @@ mmc_block-objs :=3D block.o queue.o obj-$(CONFIG_MMC_TEST) +=3D mmc_test.o obj-$(CONFIG_SDIO_UART) +=3D sdio_uart.o mmc_core-$(CONFIG_MMC_CRYPTO) +=3D crypto.o +obj-$(CONFIG_MMC_PSTORE) +=3D mmcpstore.o diff --git a/drivers/mmc/core/block.c b/drivers/mmc/core/block.c index 91e3a778d5b2..c4db0d96139b 100644 --- a/drivers/mmc/core/block.c +++ b/drivers/mmc/core/block.c @@ -3305,6 +3305,7 @@ static int mmc_blk_probe(struct mmc_card *card) } =20 mmc_blk_rpmb_add(card); + mmcpstore_card_add(card, md->disk); =20 return 0; =20 @@ -3320,6 +3321,7 @@ static void mmc_blk_remove(struct mmc_card *card) { struct mmc_blk_data *md =3D dev_get_drvdata(&card->dev); =20 + mmcpstore_card_remove(card); mmc_blk_remove_debugfs(card, md); mmc_blk_remove_parts(card, md); pm_runtime_get_sync(&card->dev); diff --git a/drivers/mmc/core/block.h b/drivers/mmc/core/block.h index 08798c56dcce..cb58c1366c1d 100644 --- a/drivers/mmc/core/block.h +++ b/drivers/mmc/core/block.h @@ -19,6 +19,20 @@ void mmc_blk_mq_complete_work(struct work_struct *work); =20 /* MMC block device helper for mmcpstore */ struct mmc_card; +struct gendisk; struct mmc_card *mmc_blk_get_card_by_name(const char *device_name); =20 +/* + * Builtin mmcpstore: called directly from mmc_blk_probe/remove. + * Module mmcpstore: stubs =E2=80=94 module uses mmc_blk_get_card_by_name(= ) instead. + */ +#if IS_BUILTIN(CONFIG_MMC_PSTORE) +void mmcpstore_card_add(struct mmc_card *card, struct gendisk *disk); +void mmcpstore_card_remove(struct mmc_card *card); +#else +static inline void mmcpstore_card_add(struct mmc_card *card, + struct gendisk *disk) {} +static inline void mmcpstore_card_remove(struct mmc_card *card) {} +#endif + #endif diff --git a/drivers/mmc/core/mmcpstore.c b/drivers/mmc/core/mmcpstore.c new file mode 100644 index 000000000000..8be94ea179f3 --- /dev/null +++ b/drivers/mmc/core/mmcpstore.c @@ -0,0 +1,1519 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MMC pstore support based on pstore/zone API + * + * This driver provides pstore backend support for non-removable MMC devic= es (eMMC), + * allowing kernel crash logs and other pstore data to be stored + * on eMMC storage devices. Only works with non-removable cards. + * + */ + +#define pr_fmt(fmt) "mmcpstore: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "bus.h" +#include +#include +#include +#include +#include +#include +#include +#include "block.h" +#include "card.h" +#include "core.h" +#include "mmc_ops.h" + +#define MMC_PSTORE_SECTOR_SIZE 512 +#define MMC_PSTORE_MAX_BUFFER_SIZE (256 * 1024) +#define MMC_PSTORE_BYTES_TO_KB(bytes) ((bytes) / 1024) +#define MMC_PSTORE_METADATA_SIZE 1024 +#define MMC_PSTORE_DATA_TIMEOUT_NS 2000000000U +#define MMC_PSTORE_DEVICE_NAME_SIZE 32 /* Size for device name buffers */ +#define MMC_PSTORE_DEV_PREFIX "/dev/" +#define MMC_PSTORE_DEV_PREFIX_LEN 5 /* Length of "/dev/" */ +#define MMC_PSTORE_PANIC_STABILIZE_DELAY_MS 5 +#define MMC_PSTORE_PANIC_DELAY_MS 50 +#define MMC_PSTORE_HARDWARE_TIMEOUT_MS 100 + +static unsigned long part_sect_ofs; +module_param_named(sector_offset, part_sect_ofs, ulong, 0644); +MODULE_PARM_DESC(sector_offset, "Sector offset within partition to start p= store storage"); + +static unsigned long part_sect_cnt; +module_param_named(sector_count, part_sect_cnt, ulong, 0644); +MODULE_PARM_DESC(sector_count, "Number of sectors to use for pstore storag= e"); + +/** + * struct mmcpstore_context - MMC pstore context + * @dev: pstore device info for registration + * @card: MMC card associated with this pstore + * @card_name: Registered device path for block I/O operations (e.g., "/de= v/mmcblk1p5") + * Set once during registration and used for block device acce= ss + * @start_sect: Starting sector for pstore data + * @size: Total size available for pstore + * @lock: Mutex to protect MMC operations + * @buffer: Pre-allocated buffer for alignment handling + * @buffer_size: Size of the pre-allocated buffer + * @state: Current state of the context + * @original_part_config: Original partition config before pstore operatio= ns + * @part_config_saved: Whether we've saved the original partition config + */ +struct mmcpstore_context { + struct pstore_device_info dev; + struct mmc_card *card; + char card_name[MMC_PSTORE_DEVICE_NAME_SIZE]; + sector_t start_sect; + sector_t size; + struct mutex lock; /* Protects MMC operations */ + char *buffer; + size_t buffer_size; + enum { + MMCPSTORE_STATE_UNINITIALIZED, + MMCPSTORE_STATE_INITIALIZING, + MMCPSTORE_STATE_READY, + MMCPSTORE_STATE_SUSPENDED, + MMCPSTORE_STATE_SHUTDOWN, + MMCPSTORE_STATE_ERROR + } state; + u8 original_part_config; + bool part_config_saved; +}; + +static struct mmcpstore_context *mmcpstore_ctx; +static DEFINE_MUTEX(mmcpstore_global_lock); + +/** + * mmcpstore_save_partition_state - Save current partition configuration + * @cxt: MMC pstore context + * + * Saves the current partition configuration so it can be restored later. + * This should be called before any pstore operations that might change pa= rtitions. + */ +static void mmcpstore_save_partition_state(struct mmcpstore_context *cxt) +{ + if (!cxt || !cxt->card || !mmc_card_mmc(cxt->card)) + return; + + if (!cxt->part_config_saved) { + cxt->original_part_config =3D cxt->card->ext_csd.part_config; + cxt->part_config_saved =3D true; + pr_debug("MMC pstore: Saved original partition config: 0x%02x\n", + cxt->original_part_config); + } +} + +/** + * mmcpstore_panic_poll_card_busy - Poll card busy via host ops + * @host: MMC host structure + * @timeout_ms: Timeout in milliseconds + * + * Uses the host's card_busy op to poll until the card signals not-busy. + * Returns: 0 if ready, -ETIMEDOUT if timeout, -ENODEV if no card_busy op + */ +static int mmcpstore_panic_poll_card_busy(struct mmc_host *host, int timeo= ut_ms) +{ + int loops =3D timeout_ms * 1000 / 100; + int i; + + if (!host->ops || !host->ops->card_busy) + return -ENODEV; + + for (i =3D 0; i < loops; i++) { + if (!host->ops->card_busy(host)) + return 0; + udelay(100); + } + + return -ETIMEDOUT; +} + +/** + * mmcpstore_panic_switch_partition - Panic-safe partition switch using di= rect CMD6 + * @cxt: MMC pstore context + * @target_part: Target partition number (0 =3D main user area) + * + * Sends CMD6 (SWITCH) via mmc_start_request + polling. mmc_wait_for_cmd + * cannot be used during panic because it calls wait_for_completion which + * invokes schedule() =E2=80=94 dead during panic. + * + * Returns: 0 on success, negative error on failure + */ +static int mmcpstore_panic_switch_partition(struct mmcpstore_context *cxt,= u8 target_part) +{ + struct mmc_request mrq =3D {}; + struct mmc_command cmd =3D {}; + struct mmc_host *host =3D cxt->card->host; + u8 part_config; + u8 new_part_config; + int ret; + + if (!mmc_card_mmc(cxt->card)) + return 0; + + part_config =3D cxt->card->ext_csd.part_config; + new_part_config =3D (part_config & ~EXT_CSD_PART_CONFIG_ACC_MASK) | targe= t_part; + + cmd.opcode =3D MMC_SWITCH; + cmd.arg =3D (MMC_SWITCH_MODE_WRITE_BYTE << 24) | + (EXT_CSD_PART_CONFIG << 16) | + (new_part_config << 8) | + EXT_CSD_CMD_SET_NORMAL; + cmd.flags =3D MMC_RSP_R1B | MMC_CMD_AC; + + mrq.cmd =3D &cmd; + init_completion(&mrq.completion); + mrq.done =3D NULL; + + ret =3D mmc_start_request(host, &mrq); + if (ret) + return ret; + + if (host->ops->panic_poll_completion) + host->ops->panic_poll_completion(host, &mrq); + + if (host->ops->panic_complete) + host->ops->panic_complete(host, &mrq); + + if (cmd.error) + return cmd.error; + + ret =3D mmcpstore_panic_poll_card_busy(host, MMC_PSTORE_HARDWARE_TIMEOUT_= MS); + if (ret =3D=3D -ENODEV) + mdelay(MMC_PSTORE_PANIC_DELAY_MS); + + cxt->card->ext_csd.part_config =3D new_part_config; + + return 0; +} + +/** + * mmcpstore_switch_to_main_partition - Ensure eMMC is in main partition + * @cxt: MMC pstore context + * + * Returns: 0 on success, negative error on failure + */ +static int mmcpstore_switch_to_main_partition(struct mmcpstore_context *cx= t) +{ + u8 part_config; + u8 current_part; + u8 new_part_config; + int ret; + + if (!mmc_card_mmc(cxt->card)) + return 0; + + part_config =3D cxt->card->ext_csd.part_config; + current_part =3D (part_config & EXT_CSD_PART_CONFIG_ACC_MASK); + if (current_part =3D=3D 0) + return 0; + + new_part_config =3D (part_config & ~EXT_CSD_PART_CONFIG_ACC_MASK); + ret =3D mmc_switch(cxt->card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_PART_CONFIG, new_part_config, + cxt->card->ext_csd.part_time); + if (ret) { + pr_err("MMC pstore: Failed to switch to main partition: %d\n", ret); + return ret; + } + + pr_debug("MMC pstore: Switched from partition %u to main partition\n", cu= rrent_part); + return 0; +} + +/** + * mmcpstore_do_request_internal - Perform single MMC read/write request + * @buf: Buffer for data + * @size: Size of data in bytes (must be <=3D max_req_size) + * @sect_offset: Sector offset on the MMC device + * @write: true for write, false for read + * @panic: true if called during panic (polling mode) + * + * Internal function that performs a single MMC request without splitting. + * Size must not exceed host->max_req_size. + * + * Returns: 0 on success, negative error on failure + */ +static int mmcpstore_do_request_internal(char *buf, size_t size, + loff_t sect_offset, bool write, + bool panic) +{ + struct mmcpstore_context *cxt =3D mmcpstore_ctx; + struct mmc_request mrq =3D {}; + struct mmc_command cmd =3D {}; + struct mmc_command sbc =3D {}; + struct mmc_data data =3D {}; + struct scatterlist sg; + struct mmc_host *host; + u32 blocks =3D size >> 9; + u32 opcode; + u32 status; + int ret; + int start_ret; + + if (!cxt || !cxt->card) + return -ENODEV; + + if (sect_offset + blocks > (cxt->size >> 9)) { + if (panic) + pr_info("PANIC: Request exceeds partition: offset=3D%lld, blocks=3D%u, = sectors=3D%llu\n", + sect_offset, blocks, cxt->size >> 9); + else + pr_err("Request exceeds partition: offset=3D%lld, blocks=3D%u, sectors= =3D%llu\n", + sect_offset, blocks, cxt->size >> 9); + return -EINVAL; + } + + if (size % MMC_PSTORE_SECTOR_SIZE) { + pr_err("Size must be multiple of %d bytes\n", MMC_PSTORE_SECTOR_SIZE); + return -EINVAL; + } + + host =3D cxt->card->host; + + /* + * Check against host's max_req_size to prevent request failures. + * The block layer handles this automatically, but we're bypassing it + * by calling mmc_start_request/mmc_wait_for_req directly. + * Large panic dumps (8MB+) can exceed this limit on some platforms. + */ + if (size > host->max_req_size) { + if (panic) + pr_info("PANIC: Request size %zu exceeds max_req_size %u, needs splitti= ng\n", + size, host->max_req_size); + else + pr_err("Request size %zu exceeds max_req_size %u\n", + size, host->max_req_size); + return -EINVAL; + } + + if (write) { + opcode =3D (blocks > 1) ? MMC_WRITE_MULTIPLE_BLOCK : + MMC_WRITE_BLOCK; + data.flags =3D MMC_DATA_WRITE; + } else { + opcode =3D (blocks > 1) ? MMC_READ_MULTIPLE_BLOCK : + MMC_READ_SINGLE_BLOCK; + data.flags =3D MMC_DATA_READ; + } + + cmd.opcode =3D opcode; + if (mmc_card_is_blockaddr(cxt->card)) { + cmd.arg =3D cxt->start_sect + sect_offset; + } else { + cmd.arg =3D (cxt->start_sect + sect_offset) * + MMC_PSTORE_SECTOR_SIZE; + } + + pr_debug("MMC request: opcode=3D%u, arg=3D%u, blocks=3D%u, size=3D%zu, se= ct_offset=3D%lld, start_sect=3D%llu\n", + opcode, cmd.arg, blocks, size, sect_offset, cxt->start_sect); + + cmd.flags =3D MMC_RSP_R1 | MMC_CMD_ADTC; + + /* + * Use CMD23 (SET_BLOCK_COUNT) for all pstore operations on eMMC. + * For write operations, set bit 24 (Forced Programming) to bypass eMMC c= ache + * and write directly to flash. This provides better reliability for all + * pstore writes including single-block pmsg writes, ensuring data persis= tence + * without requiring explicit cache flush commands. + * + * Note: CMD23 is mandatory for eMMC 4.41+ (and optional for earlier vers= ions). + */ + if (mmc_card_mmc(cxt->card) && (host->caps & MMC_CAP_CMD23)) { + sbc.opcode =3D MMC_SET_BLOCK_COUNT; + sbc.arg =3D blocks; + + /* Set Forced Programming bit (bit 24) for writes to bypass cache */ + if (write) + sbc.arg |=3D BIT(24); + + sbc.flags =3D MMC_RSP_R1 | MMC_CMD_AC; + mrq.sbc =3D &sbc; + + pr_debug("Using CMD23: blocks=3D%u, forced_prog=3D%d\n", + blocks, write ? 1 : 0); + } + + data.blksz =3D MMC_PSTORE_SECTOR_SIZE; + data.blocks =3D blocks; + sg_init_one(&sg, buf, size); + data.sg =3D &sg; + data.sg_len =3D 1; + mrq.cmd =3D &cmd; + mrq.data =3D &data; + + mmc_set_data_timeout(&data, cxt->card); + data.timeout_ns =3D MMC_PSTORE_DATA_TIMEOUT_NS; + + if (!panic) { + mutex_lock(&cxt->lock); + mmc_claim_host(host); + + /* Disable command queue engine for pstore operations */ + if (cxt->card->ext_csd.cmdq_en) { + ret =3D mmc_cmdq_disable(cxt->card); + if (ret) { + pr_err("MMC pstore: Failed to disable CQE: %d\n", ret); + mmc_release_host(host); + mutex_unlock(&cxt->lock); + return ret; + } + /* Mark that we need to re-enable CQE on cleanup */ + cxt->card->reenable_cmdq =3D true; + } + + /* + * Ensure we're in the main partition (not boot/RPMB) for eMMC + */ + ret =3D mmcpstore_switch_to_main_partition(cxt); + if (ret) { + if (cxt->card->reenable_cmdq && + !cxt->card->ext_csd.cmdq_en) + mmc_cmdq_enable(cxt->card); + mmc_release_host(host); + mutex_unlock(&cxt->lock); + return ret; + } + + ret =3D mmc_send_status(cxt->card, &status); + if (ret) { + pr_info("MMC pstore: Card status check failed: %d\n", ret); + } else if (R1_CURRENT_STATE(status) !=3D R1_STATE_TRAN) { + pr_info("MMC pstore: Card not in transfer state (status=3D0x%08x, state= =3D%d)\n", + status, R1_CURRENT_STATE(status)); + } + } else { + /* + * PANIC MODE: Force claim host for exclusive access during panic + * Background processes may be stuck/dead, so we force claim instead of = waiting + */ + mmc_panic_claim_host(host); + + if (mmc_card_mmc(cxt->card)) { + u8 part_config =3D cxt->card->ext_csd.part_config; + u8 current_part =3D (part_config & EXT_CSD_PART_CONFIG_ACC_MASK); + + if (current_part !=3D 0) { + ret =3D mmcpstore_panic_switch_partition(cxt, 0); + if (ret) + pr_emerg("MMC pstore: Failed to switch partition: %d\n", + ret); + } + } + + /* Clear SDHCI state before starting the panic request */ + if (host->ops->panic_complete) + host->ops->panic_complete(host, &mrq); + } + + if (panic) { + init_completion(&mrq.completion); + mrq.done =3D NULL; + + start_ret =3D mmc_start_request(host, &mrq); + if (start_ret) { + pr_emerg("MMC pstore: Write failed to start: %d\n", start_ret); + cmd.error =3D start_ret; + goto panic_cleanup; + } + + /* + * Poll for completion as fallback =E2=80=94 the normal IRQ-driven + * path (sdhci_irq -> mmc_request_done) handles completion, + * but during panic IRQs may not fire. The poll checks the + * hardware registers directly. + */ + if (host->ops->panic_poll_completion) + host->ops->panic_poll_completion(host, &mrq); + + if (host->ops->panic_complete) + host->ops->panic_complete(host, &mrq); + + if (cmd.error || data.error || (mrq.stop && mrq.stop->error)) { + pr_emerg("MMC pstore: Write failed: cmd=3D%d, data=3D%d, stop=3D%d\n", + cmd.error, data.error, mrq.stop ? mrq.stop->error : 0); + } + } else { + /* + * NORMAL MODE: Use standard MMC request handling + */ + mmc_wait_for_req(host, &mrq); + } + +panic_cleanup: + + if (!panic) { + /* Re-enable command queue engine if it was enabled before */ + if (cxt->card->reenable_cmdq && !cxt->card->ext_csd.cmdq_en) + mmc_cmdq_enable(cxt->card); + + mmc_release_host(host); + mutex_unlock(&cxt->lock); + } + + ret =3D cmd.error; + if (!ret) + ret =3D data.error; + if (!ret && mrq.stop) + ret =3D mrq.stop->error; + + return ret; +} + +/** + * mmcpstore_do_request - Perform MMC read/write request with automatic sp= litting + * @buf: Buffer for data + * @size: Size of data in bytes + * @sect_offset: Sector offset from start of partition + * @write: true for write, false for read + * @panic: true if called during panic (polling mode) + * + * Wrapper that automatically splits large requests to respect host->max_r= eq_size. + * This is necessary because we bypass the block layer which normally hand= les splitting. + * + * Returns: 0 on success, negative error on failure + */ +static int mmcpstore_do_request(char *buf, size_t size, loff_t sect_offset= , bool write, bool panic) +{ + struct mmcpstore_context *cxt =3D mmcpstore_ctx; + struct mmc_host *host; + size_t max_chunk_size; + size_t remaining =3D size; + size_t offset =3D 0; + size_t chunk_size; + int ret; + + if (!cxt || !cxt->card) + return -ENODEV; + + host =3D cxt->card->host; + + /* + * Calculate max chunk size respecting both sector alignment and host lim= its. + * Align down to sector boundary to ensure each chunk is sector-aligned. + */ + max_chunk_size =3D host->max_req_size & ~(MMC_PSTORE_SECTOR_SIZE - 1); + + /* Handle small requests that fit in one transfer */ + if (size <=3D max_chunk_size) + return mmcpstore_do_request_internal(buf, size, sect_offset, write, pani= c); + + /* Split large requests into multiple transfers */ + if (panic) + pr_info("PANIC: Splitting large request: total=3D%zu, max_chunk=3D%zu\n", + size, max_chunk_size); + else + pr_debug("Splitting large request: total=3D%zu, max_chunk=3D%zu\n", + size, max_chunk_size); + + while (remaining > 0) { + chunk_size =3D min(remaining, max_chunk_size); + + ret =3D mmcpstore_do_request_internal(buf + offset, chunk_size, + sect_offset + (offset >> 9), + write, panic); + if (ret) { + if (panic) + pr_emerg("PANIC: Chunk transfer failed at offset %zu: %d\n", + offset, ret); + else + pr_err("Chunk transfer failed at offset %zu: %d\n", + offset, ret); + return ret; + } + + remaining -=3D chunk_size; + offset +=3D chunk_size; + + if (panic && remaining > 0) + pr_info("PANIC: Chunk complete, remaining=3D%zu\n", remaining); + } + + return 0; +} + +/** + * mmcpstore_check_pm_active - Check if system is in PM transition + * + * This function is designed to be deadlock-safe by using a lock-free appr= oach for + * the common case (PM state checking). The mutex is only acquired when au= to-restore + * is needed, preventing deadlocks when called from pstore read/write oper= ations + * that may already hold locks. + * + * Returns: true if PM is active, false otherwise + */ +static bool mmcpstore_check_pm_active(void) +{ + /* Check driver state - lock-free for read operations */ + bool pm_active =3D (mmcpstore_ctx && mmcpstore_ctx->state =3D=3D MMCPSTOR= E_STATE_SUSPENDED); + + return pm_active; +} + +/** + * mmcpstore_read_zone - Read data from MMC device + * @buf: Buffer to read data into + * @size: Size of data to read + * @offset: Offset in bytes from start of pstore area + * + * Returns: Number of bytes read on success, negative error on failure + */ + +static ssize_t mmcpstore_read_zone(char *buf, size_t size, loff_t offset) +{ + struct mmcpstore_context *cxt =3D mmcpstore_ctx; + loff_t aligned_offset =3D offset & ~(MMC_PSTORE_SECTOR_SIZE - 1); + char *temp_buf; + size_t read_size; + loff_t sect_offset; + bool need_copy =3D false; + int ret; + + pr_debug("%s: offset=3D%lld, size=3D%zu\n", __func__, offset, size); + + if (!cxt || cxt->state !=3D MMCPSTORE_STATE_READY || !cxt->card || !cxt->= card->host) { + pr_debug("%s: context not ready (cxt=3D%p, state=3D%d)\n", + __func__, cxt, cxt ? cxt->state : -1); + return -ENODEV; + } + + /* Check if system is in PM transition - be more aggressive */ + if (cxt->state =3D=3D MMCPSTORE_STATE_SUSPENDED || mmcpstore_check_pm_act= ive()) { + pr_debug("%s: operations blocked during PM (state=3D%d, system=3D%d)\n", + __func__, cxt->state, system_state); + return -ENODEV; + } + + /* Additional validation */ + if (!cxt->buffer || cxt->buffer_size =3D=3D 0) { + pr_err("%s: invalid buffer state\n", __func__); + return -ENODEV; + } + + if (cxt->state =3D=3D MMCPSTORE_STATE_SHUTDOWN) + return -ENODEV; + + if ((offset & (MMC_PSTORE_SECTOR_SIZE - 1)) =3D=3D 0 && + (size & (MMC_PSTORE_SECTOR_SIZE - 1)) =3D=3D 0) { + /* Already aligned - read directly into user buffer */ + temp_buf =3D buf; + read_size =3D size; + sect_offset =3D offset >> 9; + } else { + /* Need alignment handling - use pre-allocated buffer */ + read_size =3D ALIGN(offset + size, MMC_PSTORE_SECTOR_SIZE) - aligned_off= set; + sect_offset =3D aligned_offset >> 9; + + /* Check if pre-allocated buffer is large enough */ + if (read_size > cxt->buffer_size) { + pr_err("Read size %zu exceeds pre-allocated buffer size %zu\n", + read_size, cxt->buffer_size); + return -EINVAL; + } + + temp_buf =3D cxt->buffer; + need_copy =3D true; + } + + /* MMC read operation with retry for intermittent failures */ + ret =3D mmcpstore_do_request(temp_buf, read_size, sect_offset, false, + false); + if (ret) { + pr_info("%s: do_request failed: %d (offset=3D%lld, size=3D%zu, read_size= =3D%zu, sect_offset=3D%lld)\n", + __func__, ret, offset, size, read_size, sect_offset); + /* For I/O errors, return -ENOMSG to indicate empty storage */ + if (ret =3D=3D -EIO || ret =3D=3D -ETIMEDOUT) { + pr_debug("%s: MMC read failed, returning -ENOMSG\n", __func__); + return -ENOMSG; + } + return ret; + } + + /* Note: Don't check for all zeros here - let pstore_zone decide + * based on the signature. Our driver should return the actual + * data and let the upper layer validate it. + */ + + if (need_copy) + memcpy(buf, temp_buf + (offset - aligned_offset), size); + + pr_debug("%s: read successful, returning %zu bytes\n", __func__, size); + return size; +} + +/** + * mmcpstore_write_common - Common write implementation + * @buf: Buffer containing data to write + * @size: Size of data to write + * @offset: Offset in bytes from start of pstore area + * @is_panic: True for panic writes (polling mode), false for normal writes + * + * Returns: Number of bytes written on success, negative error on failure + */ +static ssize_t mmcpstore_write_common(const char *buf, size_t size, loff_t= offset, bool is_panic) +{ + struct mmcpstore_context *cxt =3D mmcpstore_ctx; + loff_t aligned_offset =3D offset & ~(MMC_PSTORE_SECTOR_SIZE - 1); + size_t aligned_size =3D ALIGN(offset + size, MMC_PSTORE_SECTOR_SIZE) - + aligned_offset; + loff_t sect_offset =3D aligned_offset >> 9; + char *temp_buf; + int ret; + + if (!cxt || cxt->state !=3D MMCPSTORE_STATE_READY) + return -ENODEV; + + /* Ensure we have saved the original partition state before any write */ + if (!is_panic) + mmcpstore_save_partition_state(cxt); + + if (cxt->state =3D=3D MMCPSTORE_STATE_SHUTDOWN) + return -ENODEV; + + /* Check if system is in PM transition - be more aggressive */ + if (cxt->state =3D=3D MMCPSTORE_STATE_SUSPENDED || mmcpstore_check_pm_act= ive()) { + pr_debug("%s: operations blocked during PM (state=3D%d, system=3D%d)\n", + __func__, cxt->state, system_state); + return -ENODEV; + } + + if (!cxt || !cxt->card) + return -ENODEV; + + /* Check bounds to prevent buffer overflow */ + if (offset + size > cxt->size) { + if (!is_panic) + pr_err("Write exceeds pstore area: offset=3D%lld, size=3D%zu, total=3D%= llu\n", + offset, size, cxt->size); + else + pr_info("PANIC: Write exceeds pstore area: offset=3D%lld, size=3D%zu, t= otal=3D%llu\n", + offset, size, cxt->size); + return -EINVAL; + } + + /* Additional safety check for large writes */ + if (size > cxt->size) { + if (!is_panic) + pr_err("Write size %zu exceeds total pstore area %llu\n", size, cxt->si= ze); + else + pr_info("PANIC: Write size %zu exceeds total pstore area %llu\n", + size, cxt->size); + return -EINVAL; + } + + if ((offset & (MMC_PSTORE_SECTOR_SIZE - 1)) =3D=3D 0 && + (size & (MMC_PSTORE_SECTOR_SIZE - 1)) =3D=3D 0) { + pr_debug("Direct write: offset=3D%lld, size=3D%zu, remaining=3D%llu\n", + offset, size, cxt->size - offset); + ret =3D mmcpstore_do_request((char *)buf, size, sect_offset, + true, is_panic); + return ret ? ret : size; + } + + if (aligned_size > cxt->buffer_size) { + if (!is_panic) + pr_err("Write size %zu exceeds buffer size %zu\n", + aligned_size, cxt->buffer_size); + else + pr_info("PANIC: Write size %zu exceeds buffer size %zu\n", + aligned_size, cxt->buffer_size); + return -EINVAL; + } + + temp_buf =3D cxt->buffer; + /* Read-modify-write for unaligned access */ + ret =3D mmcpstore_do_request(temp_buf, aligned_size, sect_offset, + false, is_panic); + if (ret) { + if (!is_panic) + pr_err("mmcpstore_write: Read for RMW failed: %d\n", + ret); + return ret; + } + + /* Copy new data into aligned buffer */ + memcpy(temp_buf + (offset - aligned_offset), buf, size); + /* Write the modified data back */ + ret =3D mmcpstore_do_request(temp_buf, aligned_size, sect_offset, + true, is_panic); + + return ret ? ret : size; +} + +/** + * mmcpstore_write - Write data to MMC device + * @buf: Buffer containing data to write + * @size: Size of data to write + * @offset: Offset in bytes from start of pstore area + * + * May be called with preemption disabled (e.g. console pstore from + * printk with console lock held). In that case, return -EBUSY so + * pstore_zone marks the zone dirty and retries from its deferred + * flush workqueue where sleeping is allowed. + * + * Returns: Number of bytes written on success, negative error on failure + */ +static ssize_t mmcpstore_write(const char *buf, size_t size, loff_t offset) +{ + if (!preemptible()) + return -EBUSY; + + return mmcpstore_write_common(buf, size, offset, false); +} + +/** + * mmcpstore_stop_all_background_io - Stop all background I/O immediately + * @cxt: MMC pstore context + * + * Aggressively stops all background I/O operations that could interfere w= ith + * panic writes. Called as early as possible during panic. + * + * Returns: 0 on success, negative error on failure + */ +static int mmcpstore_stop_all_background_io(struct mmcpstore_context *cxt) +{ + struct mmc_host *host; + int ret =3D 0; + + if (!cxt || !cxt->card || !cxt->card->host) + return -ENODEV; + + host =3D cxt->card->host; + + /* + * Hardware drain + reset FIRST =E2=80=94 this ensures the controller is + * idle and no stopped CPU is inside sdhci_irq holding host->lock. + * Only then is it safe to take the lock in mmc_panic_claim_host. + */ + if (host->ops->panic_prepare) { + ret =3D host->ops->panic_prepare(host); + if (ret) + pr_emerg("MMC pstore: panic_prepare failed: %d\n", ret); + } + + mmc_panic_claim_host(host); + + mdelay(MMC_PSTORE_PANIC_STABILIZE_DELAY_MS); + + return ret; +} + +/** + * mmcpstore_panic_write - Write data during panic with immediate I/O stop= ping + * @buf: Buffer containing data to write + * @size: Size of data to write + * @offset: Offset in bytes from start of pstore area + * + * Immediately stops all background I/O and forces exclusive access to MMC + * hardware before performing the panic write to prevent corruption. + * + * Returns: Number of bytes written on success, negative error on failure + */ +static ssize_t mmcpstore_panic_write(const char *buf, size_t size, loff_t = offset) +{ + struct mmcpstore_context *cxt =3D mmcpstore_ctx; + struct mmc_host *host; + int ret; + + if (!cxt || cxt->state !=3D MMCPSTORE_STATE_READY) + return -ENODEV; + + if (offset + size > cxt->size) { + pr_emerg("MMC pstore: PANIC write exceeds pstore area: offset=3D%lld, si= ze=3D%zu, total=3D%llu\n", + offset, size, cxt->size); + return -EINVAL; + } + + ret =3D mmcpstore_stop_all_background_io(cxt); + if (ret < 0) { + pr_emerg("MMC pstore: Failed to stop background I/O: %d\n", ret); + return ret; + } + + ret =3D mmcpstore_write_common(buf, size, offset, true); + if (ret < 0) { + pr_emerg("MMC pstore: Panic write failed after stopping I/O: %d\n", ret); + return ret; + } + + mdelay(MMC_PSTORE_PANIC_STABILIZE_DELAY_MS); + + host =3D cxt->card->host; + ret =3D mmcpstore_panic_poll_card_busy(host, MMC_PSTORE_HARDWARE_TIMEOUT_= MS); + if (ret =3D=3D -ETIMEDOUT) + pr_emerg("MMC pstore: Card still busy after %dms\n", + MMC_PSTORE_HARDWARE_TIMEOUT_MS); + + mdelay(MMC_PSTORE_PANIC_DELAY_MS); + + pr_info_once("Panic write complete\n"); + return size; +} + +/* Helper function for size calculations */ +static int mmcpstore_calculate_sizes(struct mmcpstore_context *cxt, + struct pstore_blk_config *conf, + unsigned long *required_size_out, + unsigned long *kmsg_records_out) +{ + unsigned long required_size =3D 0; + unsigned long kmsg_area_size =3D 0; + unsigned long kmsg_records =3D 0; + unsigned long available_space; + unsigned long record_size; + + if (conf->pmsg_size > 0) + required_size +=3D conf->pmsg_size; + if (conf->console_size > 0) + required_size +=3D conf->console_size; + if (conf->ftrace_size > 0) + required_size +=3D conf->ftrace_size; + + if (conf->kmsg_size > 0) + kmsg_area_size =3D conf->kmsg_size; + required_size +=3D kmsg_area_size; + + if (cxt->size < required_size) { + dev_err(&cxt->card->dev, + "Effective pstore area too small (%llu < %lu bytes)\n", + cxt->size, required_size); + dev_err(&cxt->card->dev, + "Required: kmsg=3D%lu KB, pmsg=3D%lu KB, console=3D%lu KB, ftrace=3D%lu= KB\n", + conf->kmsg_size / 1024, + conf->pmsg_size / 1024, conf->console_size / 1024, + conf->ftrace_size / 1024); + return -EINVAL; + } + + /* Calculate kmsg_records based on available space */ + if (conf->kmsg_size > 0) { + available_space =3D cxt->size - (required_size - kmsg_area_size); + /* Each kmsg record needs space for data + metadata */ + record_size =3D conf->kmsg_size + MMC_PSTORE_METADATA_SIZE; + + kmsg_records =3D available_space / record_size; + pr_debug("Space calculation: total=3D%llu, available=3D%lu, record_size= =3D%lu, records=3D%lu\n", + cxt->size, available_space, record_size, kmsg_records); + + /* Check if we have enough space for at least one record */ + if (kmsg_records < 1) { + dev_err(&cxt->card->dev, + "Insufficient space for kmsg records: available=3D%lu bytes, needed=3D= %lu bytes per record\n", + available_space, record_size); + return -EINVAL; + } + } + + *required_size_out =3D required_size; + *kmsg_records_out =3D kmsg_records; + + return 0; +} + +/** + * mmcpstore_extract_base_device - Extract base device name from partition= name + * @devname: Device or partition name (e.g., "mmcblk1p5" or "mmcblk1") + * @base_name: Buffer to store the base device name + * @base_size: Size of the base_name buffer + * + * Extract the base device name from a device or partition name. + * For example: "mmcblk1p5" -> "mmcblk1", "mmcblk1" -> "mmcblk1" + * + * Returns: 0 on success, negative error on failure + */ +static int mmcpstore_extract_base_device(const char *devname, char *base_n= ame, size_t base_size) +{ + const char *p =3D devname; + const char *partition_char; + size_t len; + + if (!devname || !base_name || base_size =3D=3D 0) + return -EINVAL; + + /* Skip "/dev/" prefix if present */ + if (strncmp(p, MMC_PSTORE_DEV_PREFIX, MMC_PSTORE_DEV_PREFIX_LEN) =3D=3D 0) + p +=3D MMC_PSTORE_DEV_PREFIX_LEN; + + /* Find the 'p' that indicates partition (e.g., mmcblk1p5) */ + partition_char =3D strstr(p, "p"); + if (partition_char && partition_char > p && + isdigit(*(partition_char - 1)) && isdigit(*(partition_char + 1))) { + /* This looks like a partition name */ + len =3D partition_char - p; + } else { + /* This is likely the base device name already */ + len =3D strlen(p); + } + + if (len >=3D base_size) + return -ENAMETOOLONG; + + memcpy(base_name, p, len); + base_name[len] =3D '\0'; + + return 0; +} + +/** + * mmcpstore_extract_partno - Extract partition number from device name + * @devname: Device name (e.g., "/dev/mmcblk1p5" or "mmcblk1p5") + * + * Returns: partition number (>=3D 0) on success, -1 if no partition found + */ +static int mmcpstore_extract_partno(const char *devname) +{ + const char *p =3D devname; + const char *pp; + + if (strncmp(p, MMC_PSTORE_DEV_PREFIX, MMC_PSTORE_DEV_PREFIX_LEN) =3D=3D 0) + p +=3D MMC_PSTORE_DEV_PREFIX_LEN; + + /* Find the 'p' separator between disk name and partition number */ + for (pp =3D p + strlen(p) - 1; pp > p && isdigit(*pp); pp--) + ; + + if (*pp =3D=3D 'p' && pp > p && isdigit(*(pp - 1))) { + unsigned long partno; + int ret; + + ret =3D kstrtoul(pp + 1, 10, &partno); + if (ret) + return -1; + return partno; + } + + return -1; +} + +/** + * mmcpstore_register_for_card - Register pstore for specific MMC card + * @card: MMC card to register pstore for + * @dev_name_param: Device path (e.g., "/dev/mmcblk1p5") + * @disk: gendisk for the card (non-NULL for builtin path, NULL for module) + * + * Returns: 0 on success, negative error on failure + */ +static int mmcpstore_register_for_card(struct mmc_card *card, + const char *dev_name_param, + struct gendisk *disk) +{ + struct mmcpstore_context *cxt; + struct pstore_blk_config conf; + struct file *bdev_file; + struct block_device *bdev; + sector_t partition_sectors; + unsigned long required_size; + unsigned long kmsg_records; + int ret; + + /* + * Open the block device to get partition start sector and size. + * When called from mmc_blk_probe() (builtin path), /dev/ may not + * be mounted yet, so use the gendisk + partition number to look + * up the dev_t directly. + */ + if (disk) { + int partno =3D mmcpstore_extract_partno(dev_name_param); + dev_t devt; + + if (partno < 0) { + pr_warn("Cannot parse partition from %s\n", + dev_name_param); + return -EINVAL; + } + devt =3D part_devt(disk, partno); + if (!devt) { + pr_warn("Partition %d not found on %s\n", + partno, disk->disk_name); + return -ENODEV; + } + bdev_file =3D bdev_file_open_by_dev(devt, BLK_OPEN_READ, + NULL, NULL); + } else { + bdev_file =3D bdev_file_open_by_path(dev_name_param, + BLK_OPEN_READ, NULL, NULL); + } + if (IS_ERR(bdev_file)) { + ret =3D PTR_ERR(bdev_file); + pr_warn("Failed to open device %s: %d\n", dev_name_param, ret); + return ret; + } + bdev =3D file_bdev(bdev_file); + + cxt =3D mmcpstore_ctx; + if (!cxt) { + pr_err("No context available\n"); + fput(bdev_file); + return -ENODEV; + } + + /* pre-allocated buffer for alignment handling */ + cxt->buffer_size =3D MMC_PSTORE_MAX_BUFFER_SIZE; + cxt->buffer =3D kmalloc(cxt->buffer_size, GFP_KERNEL); + if (!cxt->buffer) { + pr_err("Failed to allocate %zu bytes for pstore buffer\n", + cxt->buffer_size); + fput(bdev_file); + return -ENOMEM; + } + + if (!(card->host->caps & MMC_CAP_NONREMOVABLE)) { + dev_err(&card->dev, "MMC pstore only supports non-removable cards (eMMC)= \n"); + dev_err(&card->dev, "This card is removable and not suitable for pstore\= n"); + ret =3D -EOPNOTSUPP; + goto err_free; + } + + /* Initialize context */ + cxt->card =3D card; + strscpy(cxt->card_name, dev_name_param, sizeof(cxt->card_name)); + cxt->start_sect =3D bdev->bd_start_sect; + cxt->size =3D bdev_nr_bytes(bdev); + + /* Apply user-specified sector offset and count within the partition */ + if (part_sect_ofs > 0 || part_sect_cnt > 0) { + if ((part_sect_ofs > 0 && part_sect_cnt =3D=3D 0) || + (part_sect_ofs =3D=3D 0 && part_sect_cnt > 0)) { + dev_err(&card->dev, "sector_offset and sector_count must be specified\n= "); + ret =3D -EINVAL; + goto err_free; + } + + /* Validate offset and count */ + partition_sectors =3D cxt->size >> 9; + + if (part_sect_ofs >=3D partition_sectors) { + dev_err(&card->dev, "Sector offset %lu >=3D partition size %llu sectors= \n", + part_sect_ofs, partition_sectors); + ret =3D -EINVAL; + goto err_free; + } + + if (part_sect_ofs + part_sect_cnt > partition_sectors) { + dev_err(&card->dev, "Sector range %lu-%lu exceeds partition size %llu s= ectors\n", + part_sect_ofs, + part_sect_ofs + part_sect_cnt - 1, + partition_sectors); + ret =3D -EINVAL; + goto err_free; + } + + cxt->start_sect +=3D part_sect_ofs; + cxt->size =3D part_sect_cnt * MMC_PSTORE_SECTOR_SIZE; + + pr_info("Pstore will use: sectors %lu-%lu (%llu bytes total)\n", + part_sect_ofs, part_sect_ofs + part_sect_cnt - 1, + cxt->size); + } + + fput(bdev_file); + + /* Configure pstore */ + memset(&conf, 0, sizeof(conf)); + strscpy(conf.device, dev_name_param, sizeof(conf.device)); + conf.max_reason =3D KMSG_DUMP_PANIC; + + /* Fetch pstore configuration including sizes from module parameters */ + ret =3D pstore_blk_get_config(&conf); + if (ret !=3D 0) { + pr_err("Failed to get pstore block config: %d\n", ret); + goto err_free; + } + + /* Validate size requirements */ + ret =3D mmcpstore_calculate_sizes(cxt, &conf, &required_size, + &kmsg_records); + if (ret) + goto err_free; + + pr_debug("Pstore requirements: kmsg=3D%lu KB, pmsg=3D%lu KB, console=3D%l= u KB, ftrace=3D%lu KB\n", + MMC_PSTORE_BYTES_TO_KB(conf.kmsg_size), + MMC_PSTORE_BYTES_TO_KB(conf.pmsg_size), + MMC_PSTORE_BYTES_TO_KB(conf.console_size), + MMC_PSTORE_BYTES_TO_KB(conf.ftrace_size)); + pr_debug("Pstore capacity: ~%lu kmsg records possible (%lu KB each, varie= s with compression)\n", + kmsg_records, MMC_PSTORE_BYTES_TO_KB(conf.kmsg_size)); + + /* Set up pstore device info */ + cxt->dev.flags =3D 0; /* Support all backends */ + if (conf.kmsg_size > 0) + cxt->dev.flags |=3D PSTORE_FLAGS_DMESG; + if (conf.pmsg_size > 0) + cxt->dev.flags |=3D PSTORE_FLAGS_PMSG; + if (conf.console_size > 0) + cxt->dev.flags |=3D PSTORE_FLAGS_CONSOLE; + if (conf.ftrace_size > 0) + cxt->dev.flags |=3D PSTORE_FLAGS_FTRACE; + + /* Set up zone structure for pstore/zone API */ + cxt->dev.zone.read =3D mmcpstore_read_zone; + cxt->dev.zone.write =3D mmcpstore_write; + cxt->dev.zone.panic_write =3D mmcpstore_panic_write; + cxt->dev.zone.total_size =3D cxt->size; + cxt->dev.zone.kmsg_size =3D conf.kmsg_size; + cxt->dev.zone.pmsg_size =3D conf.pmsg_size; + cxt->dev.zone.console_size =3D conf.console_size; + cxt->dev.zone.ftrace_size =3D conf.ftrace_size; + cxt->dev.zone.max_reason =3D conf.max_reason; + cxt->dev.zone.name =3D "mmcpstore"; + cxt->dev.zone.owner =3D THIS_MODULE; + + mutex_init(&cxt->lock); + + /* + * Set state to READY before registration because register_pstore_device() + * triggers pstore_zone recovery which calls our read callback, and the + * read callback requires state =3D=3D READY to proceed. + */ + mutex_lock(&mmcpstore_global_lock); + cxt->state =3D MMCPSTORE_STATE_READY; + mutex_unlock(&mmcpstore_global_lock); + + ret =3D register_pstore_device(&cxt->dev); + if (ret) { + dev_err(&card->dev, "Failed to register pstore device: %d\n", + ret); + mutex_lock(&mmcpstore_global_lock); + cxt->state =3D MMCPSTORE_STATE_UNINITIALIZED; + mutex_unlock(&mmcpstore_global_lock); + goto err_free; + } + + dev_info(&card->dev, "MMC pstore backend registered successfully\n"); + dev_info(&card->dev, "Device: %s, Size: %llu bytes (%llu KB)\n", + dev_name_param, cxt->size, MMC_PSTORE_BYTES_TO_KB(cxt->size)); + dev_info(&card->dev, "Start sector: %llu, Sector count: %llu\n", + cxt->start_sect, cxt->size / MMC_PSTORE_SECTOR_SIZE); + dev_info(&card->dev, "Pstore components: kmsg=3D%lu KB, pmsg=3D%lu KB, co= nsole=3D%lu KB, ftrace=3D%lu KB\n", + MMC_PSTORE_BYTES_TO_KB(conf.kmsg_size), + MMC_PSTORE_BYTES_TO_KB(conf.pmsg_size), + MMC_PSTORE_BYTES_TO_KB(conf.console_size), + MMC_PSTORE_BYTES_TO_KB(conf.ftrace_size)); + + return 0; + +err_free: + kfree(cxt->buffer); + /* Don't free cxt here - it's the global context */ + return ret; +} + +/** + * mmcpstore_unregister_device - Unregister pstore backend + */ +static void mmcpstore_unregister_device(void) +{ + struct mmcpstore_context *cxt; + + mutex_lock(&mmcpstore_global_lock); + + cxt =3D mmcpstore_ctx; + if (!cxt) + goto out_unlock; + + cxt->state =3D MMCPSTORE_STATE_SHUTDOWN; + unregister_pstore_device(&cxt->dev); + mmcpstore_ctx =3D NULL; + kfree(cxt->buffer); + kfree(cxt); + + pr_info("Self-unregistered MMC pstore backend\n"); + +out_unlock: + mutex_unlock(&mmcpstore_global_lock); +} + +#ifndef MODULE +/** + * mmcpstore_card_add - Called when an MMC card is probed (builtin path) + * @card: The MMC card that was just probed + * @disk: The gendisk associated with the card's block device + * + * Called from mmc_blk_probe() when a new card is detected. Checks if + * this card matches the configured pstore_blk device and registers + * the pstore backend if so. Only compiled when CONFIG_MMC_PSTORE=3Dy. + */ +void mmcpstore_card_add(struct mmc_card *card, struct gendisk *disk) +{ + struct pstore_blk_config conf; + char base_device[MMC_PSTORE_DEVICE_NAME_SIZE]; + char card_dev[MMC_PSTORE_DEVICE_NAME_SIZE]; + + if (pstore_blk_get_config(&conf) !=3D 0 || !conf.device[0]) + return; + + if (mmcpstore_extract_base_device(conf.device, base_device, + sizeof(base_device)) !=3D 0) + return; + + snprintf(card_dev, sizeof(card_dev), "mmcblk%d", + card->host->index); + + if (strcmp(card_dev, base_device) !=3D 0) + return; + + if (!(card->host->caps & MMC_CAP_NONREMOVABLE)) { + pr_info("card %s is removable, skipping pstore\n", + mmc_card_id(card)); + return; + } + + if (!mmcpstore_ctx) { + mmcpstore_ctx =3D kzalloc_obj(*mmcpstore_ctx, GFP_KERNEL); + if (!mmcpstore_ctx) + return; + } + + mmcpstore_ctx->state =3D MMCPSTORE_STATE_INITIALIZING; + mmcpstore_register_for_card(card, conf.device, disk); +} + +/** + * mmcpstore_card_remove - Called when an MMC card is removed (builtin pat= h) + * @card: The MMC card being removed + * + * Called from mmc_blk_remove(). Unregisters pstore if this card + * was the active pstore backend. Only compiled when CONFIG_MMC_PSTORE=3Dy. + */ +void mmcpstore_card_remove(struct mmc_card *card) +{ + mutex_lock(&mmcpstore_global_lock); + if (!mmcpstore_ctx || mmcpstore_ctx->card !=3D card) { + mutex_unlock(&mmcpstore_global_lock); + return; + } + mutex_unlock(&mmcpstore_global_lock); + + mmcpstore_unregister_device(); +} +#else /* MODULE */ +/** + * mmcpstore_find_and_register - Find eMMC card and register pstore (modul= e path) + * + * When loaded as a module, the eMMC card is already probed. Use + * mmc_blk_get_card_by_name() to look up the card by device name + * and register the pstore backend. + * + * Returns: 0 on success, negative error on failure + */ +static int mmcpstore_find_and_register(void) +{ + struct pstore_blk_config conf; + struct mmc_card *card; + char base_device[MMC_PSTORE_DEVICE_NAME_SIZE]; + + if (pstore_blk_get_config(&conf) !=3D 0 || !conf.device[0]) { + pr_info("no pstore_blk device configured\n"); + return 0; + } + + if (mmcpstore_extract_base_device(conf.device, base_device, + sizeof(base_device)) !=3D 0) + return -EINVAL; + + card =3D mmc_blk_get_card_by_name(base_device); + if (!card) { + pr_err("MMC device %s not found\n", base_device); + return -ENODEV; + } + + if (!(card->host->caps & MMC_CAP_NONREMOVABLE)) { + pr_info("card %s is removable, skipping pstore\n", + mmc_card_id(card)); + return -EINVAL; + } + + mmcpstore_ctx =3D kzalloc_obj(*mmcpstore_ctx, GFP_KERNEL); + if (!mmcpstore_ctx) + return -ENOMEM; + + mmcpstore_ctx->state =3D MMCPSTORE_STATE_INITIALIZING; + return mmcpstore_register_for_card(card, conf.device, NULL); +} +#endif /* MODULE */ + +/** + * mmcpstore_pm_notifier - PM notifier for early PM detection + * @nb: Notifier block + * @action: PM action + * @unused: Unused parameter + * + * Returns: NOTIFY_OK + */ +#ifdef CONFIG_PM_SLEEP +static int mmcpstore_pm_notifier(struct notifier_block *nb, unsigned long = action, void *unused) +{ + int ret; + + switch (action) { + case PM_SUSPEND_PREPARE: + /* Set state to SUSPENDED early in PM process to block pstore operations= */ + mutex_lock(&mmcpstore_global_lock); + if (mmcpstore_ctx && mmcpstore_ctx->state =3D=3D MMCPSTORE_STATE_READY) { + mmcpstore_ctx->state =3D MMCPSTORE_STATE_SUSPENDED; + pr_info("PM suspend prepare - blocking pstore operations\n"); + } + mutex_unlock(&mmcpstore_global_lock); + break; + case PM_POST_SUSPEND: + mutex_lock(&mmcpstore_global_lock); + if (mmcpstore_ctx && mmcpstore_ctx->state =3D=3D MMCPSTORE_STATE_SUSPEND= ED) { + struct mmc_card *card =3D mmcpstore_ctx->card; + + if (!card || !card->host) { + pr_warn("No card present for resume initialization\n"); + mutex_unlock(&mmcpstore_global_lock); + break; + } + + /* Force card initialization via runtime PM */ + ret =3D pm_runtime_get_sync(&card->dev); + if (ret < 0) { + pr_err("Failed to runtime resume card: %d\n", ret); + pm_runtime_put_noidle(&card->dev); + mutex_unlock(&mmcpstore_global_lock); + break; + } + + /* Release PM reference, card stays active due to autosuspend */ + pm_runtime_mark_last_busy(&card->dev); + pm_runtime_put_autosuspend(&card->dev); + + mmcpstore_ctx->state =3D MMCPSTORE_STATE_READY; + pr_info("PM resume: card initialized, pstore operations restored\n"); + } + mutex_unlock(&mmcpstore_global_lock); + break; + default: + break; + } + return NOTIFY_OK; +} +#else +static int mmcpstore_pm_notifier(struct notifier_block *nb, unsigned long = action, void *unused) +{ + return NOTIFY_DONE; +} +#endif + +static struct notifier_block mmcpstore_pm_nb =3D { + .notifier_call =3D mmcpstore_pm_notifier, + .priority =3D INT_MAX, /* Highest priority for early detection */ +}; + +/** + * mmcpstore_reboot_notifier - Handle system reboot/shutdown + * @nb: notifier block + * @action: reboot action (SYS_RESTART, SYS_HALT, SYS_POWER_OFF) + * @data: unused + * + * Unregisters pstore backend before system shutdown to prevent + * hanging during reboot when printk.always_kmsg_dump=3D1 is set. + */ +static int mmcpstore_reboot_notifier(struct notifier_block *nb, unsigned l= ong action, void *data) +{ + switch (action) { + case SYS_RESTART: + case SYS_HALT: + case SYS_POWER_OFF: + pr_info("System shutdown detected, unregistering pstore\n"); + mutex_lock(&mmcpstore_global_lock); + if (mmcpstore_ctx) + mmcpstore_ctx->state =3D MMCPSTORE_STATE_SHUTDOWN; + mutex_unlock(&mmcpstore_global_lock); + mmcpstore_unregister_device(); + break; + default: + break; + } + return NOTIFY_DONE; +} + +static struct notifier_block mmcpstore_reboot_nb =3D { + .notifier_call =3D mmcpstore_reboot_notifier, +}; + +/** + * mmcpstore_init - Initialize MMC pstore driver + * + * Returns: 0 on success, negative error on failure + */ +static int __init mmcpstore_init(void) +{ + int ret; + + pr_info("MMC pstore driver initializing\n"); + + mmcpstore_ctx =3D NULL; + + ret =3D register_reboot_notifier(&mmcpstore_reboot_nb); + if (ret) { + pr_err("Failed to register reboot notifier: %d\n", ret); + return ret; + } + + ret =3D register_pm_notifier(&mmcpstore_pm_nb); + if (ret) { + pr_err("Failed to register PM notifier: %d\n", ret); + unregister_reboot_notifier(&mmcpstore_reboot_nb); + return ret; + } + +#ifdef MODULE + /* + * Module: eMMC card is already probed, look it up and register now. + * Builtin: mmc_blk_probe() will call mmcpstore_card_add() later. + */ + ret =3D mmcpstore_find_and_register(); + if (ret) { + unregister_pm_notifier(&mmcpstore_pm_nb); + unregister_reboot_notifier(&mmcpstore_reboot_nb); + return ret; + } +#endif + + return 0; +} + +/** + * mmcpstore_exit - Cleanup MMC pstore driver + */ +static void __exit mmcpstore_exit(void) +{ + pr_info("Unregistering MMC pstore driver\n"); + + unregister_pm_notifier(&mmcpstore_pm_nb); + unregister_reboot_notifier(&mmcpstore_reboot_nb); + + mutex_lock(&mmcpstore_global_lock); + if (mmcpstore_ctx) { + mmcpstore_ctx->state =3D MMCPSTORE_STATE_SHUTDOWN; + pr_info("Unregistering active pstore backend during module exit\n"); + unregister_pstore_device(&mmcpstore_ctx->dev); + kfree(mmcpstore_ctx); + mmcpstore_ctx =3D NULL; + } + mutex_unlock(&mmcpstore_global_lock); + + pr_info("MMC pstore driver unregistered\n"); +} + +module_init(mmcpstore_init); +module_exit(mmcpstore_exit); + +MODULE_AUTHOR("Kamal Dasu "); +MODULE_DESCRIPTION("MMC pstore backend driver for non-removable cards (eMM= C)"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("mmcpstore"); --=20 2.34.1