From nobody Mon Apr 6 09:19:42 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 134DB35A384 for ; Thu, 19 Mar 2026 18:59:51 +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=1773946794; cv=none; b=ULxXuMjwxDgKBGls2GyHu/dlEuT1e9mmI2w+2ZaTgUCXxg1OPSdaY7gdedeV82JwtrgBgCEVgHcKRbHqu3I0JR7vKm/hMdiRkqaZ6AYyFzu0WeRD58g0P3CC6dM50ALpTZJC5ykKPFlG4Wg2I3eyh+GVz9Bxu/SkurufsiOvdzo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773946794; c=relaxed/simple; bh=qtVua69sP2G9xkMsjuSNOM/NL0Z1s1ViGHEt7N7dPys=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=Dw/JLEGQTMwblYwJG6K5qGdbvpgRp7rhnv0zrB6uRRIzygMsQqyj0CA2SP2HUZAPPH0Zw3di/vilzJvxlSI10XmpDltFOWXVm6QW8BiyiHgJYJrdt5R5ri3sze0oeyDtqdYsjHZA3bDYoOB2aIT28Z/5BhIacr1PkLaa4aOGc5o= 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=gCl0G/n3; 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="gCl0G/n3" Received: by mail-oa1-f99.google.com with SMTP id 586e51a60fabf-40ede943bf0so808079fac.2 for ; Thu, 19 Mar 2026 11:59:51 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1773946791; x=1774551591; 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=hVUFB/990tw2Mxk5kBfPXERr8pu/rAcrWIlwWRfJBL/7BqW3Xbr/bqkjkF5nrNkz1z iAB4ltYLEa1n2LtALMWxPjlQtvO4a/OOdch/LYV2DlWFtuXjMSvKDljQZsFfCxHSvPX3 RVAFsdAmg3X7D+nCDAbYMtvndogNu7QQdKdDINH2kkp64WVTyaS7TAL+aSFh3/K8UEgd kAyotWGWp7qbrv08Bzo8skgi1sQKKr5f7132gBToqGH6JC9b0TgaAFM06KRk/qqmwn+Z 0okFL9l1Aefb0vME5sBks58vWHM2aRs8u1aeAPFcYvchSdEAZJ6I+VOC8EuYKmR9mSBt 5f4Q== X-Forwarded-Encrypted: i=1; AJvYcCUTWs5m0WeYpwz3Aa8pMXYe2Atstswl/0zGY6VWKy4zkDrhRaC1/bgunkaESP5bM8JD8fAgHdTYeQAHaR0=@vger.kernel.org X-Gm-Message-State: AOJu0YwVao0p64IpBkJ2e5klsYALLvi1MWs8eE5K7efIvvfKHRzP3chY AVf8T6fjqAmL8044/eVNsKCTdMrqJ5oP/OIKvUEVfVd4r88NDJsBOv8rOI00JvfK4y6Lo/CWQrx cogypqJ59ajN81R8JKxxad05ZlAOnzeWWKmOaOi/q3DqDUEk/oob99+tsXEg2OjSOAduZXX0T0H QTTs0uOM45gk/h+QhUJwZ3UAPxWCyyZ0MkpxgdWzY5vMUpPe2egn33hY0hGN8avZTfMDr2pHdDU 4qfZ1XtkfsXoCIu X-Gm-Gg: ATEYQzzK2nRVlHululPL5Xknmqhdz9cQYejly4XopDKDXb516B/kPmoA5pAB1vrckSH Q3y2cIAJPQwHZpFBY82mEBXZd/WcgOSLMGj/aXDd4FXcEg3iv2CdT8+qy7z2G3/WlOkp4m87h12 voTh7w0Du9e9Rr66+s2f0UlaoZskITbfOeHJl55U1eQoxQkevp3px5EPPA6GsrP7amDf341Tdwf TQxQsgnaraq/tzAP3g3pG+w1AvlImccL/70iR0ngEawM27wZj9prpfp9BfJ5gVy6dn6O9u58WQu z76XIivvgoVj4HABJokHHbk6E5a+E02fhqXo5aFP6W5hd3346aJA/cc1ZUbrXusCSpQiTVTQ7VI Q5M0v1Y4iCs4JQwEBF7ITHUAX5TO05wpc2Tk8mFPFSIW8NEV/ClJMJj3Qmzzpj8/arrnWnWk53E tjX4f6Vr30SFzZWxjW6fVPQ5urW7c+r7ovlWLrJRHUkPPJJzHdzBlIqw== X-Received: by 2002:a05:6870:5b81:b0:41c:d38:f3c5 with SMTP id 586e51a60fabf-41c11295240mr186310fac.40.1773946790784; Thu, 19 Mar 2026 11:59:50 -0700 (PDT) Received: from smtp-us-east1-p01-i01-si01.dlp.protect.broadcom.com (address-144-49-247-63.dlp.protect.broadcom.com. [144.49.247.63]) by smtp-relay.gmail.com with ESMTPS id 586e51a60fabf-41bd28b977dsm884867fac.4.2026.03.19.11.59.50 for (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 19 Mar 2026 11:59:50 -0700 (PDT) X-Relaying-Domain: broadcom.com X-CFilter-Loop: Reflected Received: by mail-dy1-f199.google.com with SMTP id 5a478bee46e88-2c0fb39b41aso15245596eec.0 for ; Thu, 19 Mar 2026 11:59:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=broadcom.com; s=google; t=1773946789; x=1774551589; 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=gCl0G/n3bG19XScO4H+ctWV/S3DrGTjsBuciMrrjKfftYg68KSiD9DTGSb5RzwepNq FTfs7i93HmhwyTjWbu2qFYWwKJVB2KDYcCciogxSMEvUtAMFhbzIfC4sbI0eoXnNrLJY tD1K7PimT0sU1Y9z+b34y+QJPJj+HtHDCc4xs= X-Forwarded-Encrypted: i=1; AJvYcCVzZD5cg6k/53gQ51N5gZmTstaHSrzgreCYnMkBAtt+ngKuvG1P7TC+CvXRqK6G19qIufTR7Q3W8JhkF2c=@vger.kernel.org X-Received: by 2002:a05:7300:8ca6:b0:2c1:778:d897 with SMTP id 5a478bee46e88-2c109752f8dmr241073eec.21.1773946789268; Thu, 19 Mar 2026 11:59:49 -0700 (PDT) X-Received: by 2002:a05:7300:8ca6:b0:2c1:778:d897 with SMTP id 5a478bee46e88-2c109752f8dmr241048eec.21.1773946788522; Thu, 19 Mar 2026 11:59:48 -0700 (PDT) Received: from mail.broadcom.net ([192.19.144.250]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2c0e55834a0sm9607220eec.14.2026.03.19.11.59.46 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 19 Mar 2026 11:59:48 -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 v3 1/4] mmc: core: Add panic-context host operations for pstore backends Date: Thu, 19 Mar 2026 14:57:02 -0400 Message-Id: <20260319185705.1516950-2-kamal.dasu@broadcom.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260319185705.1516950-1-kamal.dasu@broadcom.com> References: <20260319185705.1516950-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 Mon Apr 6 09:19:42 2026 Received: from mail-yw1-f226.google.com (mail-yw1-f226.google.com [209.85.128.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 6A2D43559C9 for ; Thu, 19 Mar 2026 18:59:55 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.226 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773946797; cv=none; b=BmereK5AsKVu+IGl5NtlJKAEpG7KphMlven1FlckTp8kv4iWaoW0q2sFtNEyVSOSc14X5P8ehzMEWP6tYLSOsgOt7nC+hIVhbc/vJIjfBDeW4snxX2R28+N6jsxwJoHPNpoQPT6dlz+2LMB02qCRjzRXr7vpPFS3/UCjwyrHfIU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773946797; c=relaxed/simple; bh=cxhSMtFnsE52QjMezqtF/D1wAEUlllDGEc4sSAeOF48=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=Csms8GVfgHa9keGjLR3GzJho+NjvlIarL9/I2yJ58+mhuyfayx+aCZbywN24Cy49L7PF2wDcdMkvZ/CFRx6IydDS1pfsw9HyWzb4tkL7iSJ337suWsDvWHXe/8yakQB8QI/7TCE5fQzM0WmzRhLY4J1NJktflpgc+q8xS9xjw3U= 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=C74nikIf; arc=none smtp.client-ip=209.85.128.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="C74nikIf" Received: by mail-yw1-f226.google.com with SMTP id 00721157ae682-79801df3e42so17906697b3.0 for ; Thu, 19 Mar 2026 11:59:55 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1773946794; x=1774551594; 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=zpun292QO0p2AqLgzYUrlOVsADRTpPXqRTdqpP531bs=; b=p4ivDLXcA0C1ODBwnpf9UOwLP6XfvYJLjN7H10yDolrw/LuqptnccmnQcxI0XrO77e BekJHerIPFInM2dbQV64FV2RRRx5NKgZeLvGvEsxjiw/TWU97gZK2/d115LoeNGJyLTQ 9z3Ifitq9YiS9RBhOekEFwHnU4z3TzDq9k6Q+UPt0pDP8an1mfSwPJEPJdmUn7zl34Ic zAq6oXtnGRJt6phFk5bXkeFpStkT43cGHpDvGLr5WPgU8NE9MuRCuV59xxICwCpU9IzR SNlrO/xKNaKOcuMC5QO8Cm1MZXAjaM2w/zPYTdTAFl7ADbZaM82f9+UcUcH37acz7H4z Tuaw== X-Forwarded-Encrypted: i=1; AJvYcCXVVeidwV3xODpY+yZraAi05t80Tlb21l8NomN+iTG5Z0buuJK7GXIoUEXaLwKtPhi8WIL1BvhB9zX+3rY=@vger.kernel.org X-Gm-Message-State: AOJu0YxfzXWi0P9cz547R6E7OXuFShzzqNr/yV+b4F8wj3oyAtFGV7Po pFgPZWGXArVGHq3mQwMoND+pX5L1NzHrNPUjCyh2wuqRABFRg1V89SDVAsOqDwTqHPwR8NW3+PP QSLKvJzyi6WtJPoAF0NT/4JWXu4nRyV1v/JTtfMLMokCB1xb82m9NY4vTlccR6oqZi7i8YMqpYO sGT/hIJ16caXBKWfUSblauVoiZgbmQh+1vmHN8LYslkUD0kiw6sGrzNeQOwDz3daNP0xio+4hxt g5F0JuRKmPqqVH/ X-Gm-Gg: ATEYQzx0FomHYJIkOfGH8+4qDrp1lWXyEVaPG4UIp0a/LjoNjNzo9oAPq5Nkh6Jbn4M GBMe8GpDLno3l362gBYfDd+SyBAZX7oDVKvwpR8bBqIOWK4wTSdAjOHjhOG8SHYta9v6UZcEFPh L8bOPRDst389cRc5M1pCSDNBItN9VNRHTnPYLIvvCCPIS9LSq6SHJAmS97k2mag7JN+5BdwLvER hhjZvBxagO/+xPWjA4XbCnb92p/YO6D+SZ9iAHKZCWAfePej2iaHXi5EkbEhHBc0V8vLXGbyCLF HA0R2HJpef//1kzt93Nf8tl6SZEfLvpCM36EfPBy8XCySlSuZ5HVeOPyMB8RG3F+OxYYlh8X3EY Um1e2pKQF0l6nT0KMIEMfCgaTOMpOgutyoW5UHKuZ/pxloB012SD6khTL14iSrSVdHUH+CJFrU8 qUh6W0eiTydUXVXv/jMv548p9GBY/TUuZgAFHrcVxIFo6NkImS+WWDTcc6 X-Received: by 2002:a05:690c:6e83:b0:79a:8fa0:85fa with SMTP id 00721157ae682-79a8fa08d16mr7743387b3.11.1773946794315; Thu, 19 Mar 2026 11:59:54 -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 00721157ae682-79a9058a1f6sm205747b3.27.2026.03.19.11.59.54 for (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 19 Mar 2026 11:59:54 -0700 (PDT) X-Relaying-Domain: broadcom.com X-CFilter-Loop: Reflected Received: by mail-dy1-f198.google.com with SMTP id 5a478bee46e88-2b81ff82e3cso860952eec.0 for ; Thu, 19 Mar 2026 11:59:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=broadcom.com; s=google; t=1773946793; x=1774551593; 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=zpun292QO0p2AqLgzYUrlOVsADRTpPXqRTdqpP531bs=; b=C74nikIfCeWgULdqFY8BGmusXfCkZg+Hom+phuQBvJPj4gsgw9ep/se9rPRQdkdHJH 9W54QfXAeCsNmv6LwINgWVGgUio2JEUtp2g53s3MZ8VvJzQOwDFKT4yEdQJg2rMaZ1Bu HR6OmoQ3wCDZzv1zuVIh3VUtL3JSFaYBZ5Xp8= X-Forwarded-Encrypted: i=1; AJvYcCXEJgk8j+pFaesJfEE+caZMiAtY0QdUFFWjlLrBnzWJqOBowTS326hnd0c13vYZUF8chY9NupAARCjl4bI=@vger.kernel.org X-Received: by 2002:a05:7301:fa0a:b0:2c0:da13:8c45 with SMTP id 5a478bee46e88-2c10974ccabmr192224eec.7.1773946792741; Thu, 19 Mar 2026 11:59:52 -0700 (PDT) X-Received: by 2002:a05:7301:fa0a:b0:2c0:da13:8c45 with SMTP id 5a478bee46e88-2c10974ccabmr192202eec.7.1773946791925; Thu, 19 Mar 2026 11:59:51 -0700 (PDT) Received: from mail.broadcom.net ([192.19.144.250]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2c0e55834a0sm9607220eec.14.2026.03.19.11.59.50 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 19 Mar 2026 11:59: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 v3 2/4] mmc: sdhci: Implement panic-context write support Date: Thu, 19 Mar 2026 14:57:03 -0400 Message-Id: <20260319185705.1516950-3-kamal.dasu@broadcom.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260319185705.1516950-1-kamal.dasu@broadcom.com> References: <20260319185705.1516950-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 | 164 ++++++++++++++++++++++++++++++++++++++- drivers/mmc/host/sdhci.h | 6 ++ 2 files changed, 166 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index ac7e11f37af7..311ab361ee32 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,147 @@ 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 + * + * 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 + * + * 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 + * + * 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 +3238,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 +3392,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 +3417,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 Mon Apr 6 09:19:42 2026 Received: from mail-qv1-f99.google.com (mail-qv1-f99.google.com [209.85.219.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 BFC643793DF for ; Thu, 19 Mar 2026 18:59:58 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.99 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773946800; cv=none; b=Dz5fPh2TgBlDiaEPE36eLadftHZIlCtJe5uckBilouJ7cWOu54PnuYCCNNHkJVXgdlvodRHvc4jtUuq+9JN4VmejSBcDxq3Gf7+ShrKZMMYuRDqGLb7MT06qYq0RynUgFbSzKhND+GNyl5JXCOpsklOL3PtM84IHmhZgPr0Ax8A= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773946800; c=relaxed/simple; bh=7AmnG/UUYqW9PbgLMpQCGJh+iAZg56GN9U6BhFeGBjM=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=fWZOZh1wC8h7rujBxZAjrIHofxsaDTVLKBq+kcFCti15hBqRo2zdwqlHIyLH4K/jHn3uWuBDBI97l4yxnMxJAg41huBDzH2ypFOjEoR248jeG1rc+KajK/BepTu1p7tAptGI3sSPCsrd7eV6wW4EubQqvypkFQyX9ex1qs+qjZw= 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=DuAwCXcI; arc=none smtp.client-ip=209.85.219.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="DuAwCXcI" Received: by mail-qv1-f99.google.com with SMTP id 6a1803df08f44-89c52db6231so12243656d6.3 for ; Thu, 19 Mar 2026 11:59:58 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1773946798; x=1774551598; 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=i9+TtOGhIG/bQsu8ldLSy/TdUpiQoAJYI6qSRaIRQlND9E/gYCjNUNRvEc1PcD1R/F +8rlKW+Z9/V+xD1JFmN0wFvibFPVIhq86p4p303FxQX4qqOdDSK8UyZpwHoko+jCXj9B QS66zpZrORt+j30K3P6a2Uw1UFCNLni1Jvb4A/baylpTR7kGbhtxhmdVpLKNm6BeErrG L+iRgX7EmKzfETtNaIrJmNn7oY9LWt65dHNTUHwaW/hXEiBQn9WVJuz5JWmHtzBDBHoN Qezes8A6sOCDgXZ3t/Z+zAVFJQmi1kfnCpv5ECngBJLkB3OkQkjGjJHkavKgtuLEVSyy 1Itw== X-Forwarded-Encrypted: i=1; AJvYcCWc0bzjGPQpvBf7y+nTzf2/4OiBfP7pvE/7qLcpAJ8oQRV3Jq2bIS8jjuMIrZwTAjleMMEtadkAga44OyM=@vger.kernel.org X-Gm-Message-State: AOJu0YzQSeusBwsPcX3JmDtly0ndn8aZb+5t2qfynDFSEHYA4gccNwe/ 7RtWVUqC3i/GKHOi2yOEARPz+zQHIVh/RRlJv+ZS5KVSOMtIilwuXcqG+Ix1Blsg7yNzJTtsJ09 Y7tyv1LuXTKZLPNWjZys7NUJxn+50H4x5FUAJtX22h0SYBc7lElRgignF4tmOUDHsxq2hargGHO EuCObekgFTKbmoXvmkJFFA77f92Ybxfr3wkoUIQxpKoDIbhs25PgluHDvcpzZNtJHJyGdD1Jrjx Vbzx3PaPaQhpiQa X-Gm-Gg: ATEYQzwnp+85jF0WG0tsb6d48TAUc768emfm80zFx9RlwnxYzVcNI7MiKaI445ovwTi 9DwNieXFmDk9/Nw4TMkh7WQ8DRxwReTJGIQNOcEewwFmh4wbqKsQj4s7hnJ6SqRohtb0dKIKQap DRfb+uJsJB8wc8Jzk5p3E9LsCC49tvhowDekgb+eV2Mkl7kuo60gpSYflezjFJ/51PLsFpGNEN1 TEjy4oqBW8MWLVFprftn/Ko0JhPSPwa83H2qStPMT5qgl4O/rq4IGGi9+TzDediGq+2b1IHzJ3V a4LaMKNboG+u2msflG3KC8CQFtNPxGPpy4ZJBcDqBZV5eozDpEiQHjOclMatKarCk75YeeVXiSO Mj2TuW7XRPjm0rXajh3DOQUU0ih8kKUEzB/YP7XdlpMdP+r9z0uloRn+yWPqrizBWxbq1C609hs Po1tVwdSuqAS5WSw9vTZYsaUNhUfadzofx4WMCV5dqyDvZO1JB3boLW1Vs X-Received: by 2002:a05:6214:5712:b0:89a:125f:37d7 with SMTP id 6a1803df08f44-89c85a6b9bcmr3560376d6.48.1773946797444; Thu, 19 Mar 2026 11:59:57 -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 6a1803df08f44-89c852f31e5sm286256d6.24.2026.03.19.11.59.57 for (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 19 Mar 2026 11:59:57 -0700 (PDT) X-Relaying-Domain: broadcom.com X-CFilter-Loop: Reflected Received: by mail-dy1-f198.google.com with SMTP id 5a478bee46e88-2bdc1b30ac8so23455039eec.1 for ; Thu, 19 Mar 2026 11:59:57 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=broadcom.com; s=google; t=1773946796; x=1774551596; 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=DuAwCXcI6dN/8lLmN22NPMdicBkDgCb3cStVa8sCCf3r1RmsXfEkjWtFPXR+qwUYl1 1bEcF2+fQkMB8jadTGfQmy9YPRN24oDsWruQep+KJH+4TJbh9c/Y5W+oRk/RHLsOkgAQ hholXVPyD04cksw+34IIb9a7cN+WmYTuS3LfQ= X-Forwarded-Encrypted: i=1; AJvYcCUgOgHD6EzraBUiZ8wXeydbA9d0CLe5TktMpOcLGMO7gFkYoldETK34f8xp/yVpN86CfioNue7KxGFMEUg=@vger.kernel.org X-Received: by 2002:a05:693c:2c13:b0:2ba:7875:10c9 with SMTP id 5a478bee46e88-2c109760e2dmr255819eec.26.1773946796133; Thu, 19 Mar 2026 11:59:56 -0700 (PDT) X-Received: by 2002:a05:693c:2c13:b0:2ba:7875:10c9 with SMTP id 5a478bee46e88-2c109760e2dmr255795eec.26.1773946795468; Thu, 19 Mar 2026 11:59:55 -0700 (PDT) Received: from mail.broadcom.net ([192.19.144.250]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2c0e55834a0sm9607220eec.14.2026.03.19.11.59.53 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 19 Mar 2026 11:59:55 -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 v3 3/4] mmc: block: Add helper to look up mmc_card by device name Date: Thu, 19 Mar 2026 14:57:04 -0400 Message-Id: <20260319185705.1516950-4-kamal.dasu@broadcom.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260319185705.1516950-1-kamal.dasu@broadcom.com> References: <20260319185705.1516950-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 Mon Apr 6 09:19:42 2026 Received: from mail-dy1-f225.google.com (mail-dy1-f225.google.com [74.125.82.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 161B535A384 for ; Thu, 19 Mar 2026 19:00:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.225 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773946807; cv=none; b=Nfc4PlTYyFivwPysDsQT3p72ky4RQh2Co7vO0A2VtR4e5JEl0eVMDePd2N3f/08A1hxJ9XjsNjZ4ZKpeECAsIMenQiQIh+os9esVeZCOPrHJD7A9S60jgI1/tqS3oIgzLppwNZErhMI+NE6bh7LuOy7CDpg5VhlNKxp9+r2WvGA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773946807; c=relaxed/simple; bh=Y5AfawbVRYAUzqIPb60ny7+MNAgDQm2/0hBDoSkE7Ps=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=H9HX0m4mn92vl6+PT6YojW6UMC/2QFasuFf/pAGaE9jZ35PUfIywgjTBWNfxel+33P4ETZXX743t79rphxZ7EYylbJVedPihbBNb4tEvLnxvMLVRPkQve0zH61tXEVS37k/V1ayAx44wsZrqyp1bmBgC8efIxfQ8wFx0JewyiRE= 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=ICX8iu2h; arc=none smtp.client-ip=74.125.82.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="ICX8iu2h" Received: by mail-dy1-f225.google.com with SMTP id 5a478bee46e88-2c0bcd8f194so1880390eec.1 for ; Thu, 19 Mar 2026 12:00:03 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1773946803; x=1774551603; 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=kEqAae82XXuuHmZzJPNyH/fvF1ccydo/Gc31OLeRFD8=; b=GSqAVMClZGYYy1xjKWzZ4AArZLSxQInMSc1fUaYaBuhSMzLECcHGJzAF6xGwXiAlIt qiGLj4jJs2qc5KssSq8ag1fzJ44ZIxSgH7YHPwjP+Ts4uk/ALa1IIzq39XBC5DaEVjKc VVGPOlO2oLHKK3j1tZp0a1Ngga3lppd+KX97uOUpkaA3I80tyAEOO7DrAIkSP3dvMfPb Z6NLB+A6mPYqRU3Os9pj3/aGrRWIJdCaGvlVML7lxa7hkpp/NJklwhtTj/IOLhDcYOC5 g/hIuXUStH4pIb3bjrWQIvGgiD1KN3HSbP9wJmm/ivp8+lPuVb+zGcAi7hvzYrlQjS2Z 76BA== X-Forwarded-Encrypted: i=1; AJvYcCXDwJAtxKgnx1mIG+v7+oD0I1SR1VduCRZGh8LDVRmlZNIFbcXcsOGmDmbRJlICz9HVzV/+SBr02T9KUo4=@vger.kernel.org X-Gm-Message-State: AOJu0YymKbrKCPgooR2SvdsPSv1eeWmGEXbwuXd3J3/tSyz3U+8SV2Ky HC0tsYcrYRJgOyVNdpulIFIRn3IT0zxvLo4d6E6IF3i4DI1CnxUPJCFkl1ea11o3Srd6bRwWh4S cvz8R1Cxbo25Gu99H0rG/Y1FFEFaq+zTQf5tLVLPSvwF1mkVChMXqLl9yzyb7iGrUAf4+DMOkkb AvqiKic+Cw4eehevQEktZtxSPkEy6QejjOE4Y7gdxO2MwDEGkBG9REOyQ5mR83prOi845AGIWo4 LbL7RxYD/NCaVMm X-Gm-Gg: ATEYQzyN/pDR9t8JYwEFxG/ey5gDj3k903RSplDZjlbN7QTDEjebanOQnPezsW0eVrW 88sNdANLTCj/567b/XN0Zt/1htqhlnWIFErypdwNCi6iFCx4BGlzWYC92pcRS+GruYoehrCQ4aZ p/EKwqBXGHZJsGBU1VMIpbal/tkW2+UbejoOVFyEHJQtUWqdifnmbhvR2RrVoTbTbqyHjz5IJC8 XgN3rbjYFs+j4xej0kt3zXNuu/dsWetxO3uO0klOI6/ejhnwXst8hRH+9N/qvZkvfwNnMQG2ghb sbxPonQJrFGzBQalqF2lx8T7G7iFk5vIoA0yJfvBbCrc05NN+/nPlk7Wsx8ryj8irHbfYgPRbKo GLVlhCi8L5am7vHfiFopKKIM6CpuHWE1Ne3R9lb7C41xv03U+9abJZWL7zQFgAuVt5AAwE+J8/E DYqFajREVK7/RitYGfkohpO+K9n6Fyw+9IRwHjMozKGPdvySqoZlKxmvdb X-Received: by 2002:a05:7300:cb87:b0:2c0:ba91:48b6 with SMTP id 5a478bee46e88-2c10976028cmr248891eec.26.1773946802843; Thu, 19 Mar 2026 12:00:02 -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 5a478bee46e88-2c0e54bfd6esm569009eec.5.2026.03.19.12.00.02 for (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 19 Mar 2026 12:00:02 -0700 (PDT) X-Relaying-Domain: broadcom.com X-CFilter-Loop: Reflected Received: by mail-dy1-f197.google.com with SMTP id 5a478bee46e88-2c0bddb9196so832362eec.1 for ; Thu, 19 Mar 2026 12:00:01 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=broadcom.com; s=google; t=1773946801; x=1774551601; 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=kEqAae82XXuuHmZzJPNyH/fvF1ccydo/Gc31OLeRFD8=; b=ICX8iu2hZARqcdUoKM87AnmVBB61Jy2qKO/ecb1M0XTtl1Sx1VVdrJ2Z5TfmtIOIOQ KY0LDKJl76YLR6qAV3QWCHDG5GsoeBexn/8ZWZPAsIDAWMvtVlGevS6mqiREIauqVnCQ U3s2M5atnjDy+52ASx9apaG/rN8kUnk7bx798= X-Forwarded-Encrypted: i=1; AJvYcCWDZDz3GlU2gAHnzIWvOWZmA5SmB0FCu20v1uWWv8+3dzPmX8g3lHKj+A4YyJbn6/C42RBxKtVcVDEpTpk=@vger.kernel.org X-Received: by 2002:a05:7300:f68c:b0:2c0:af67:e908 with SMTP id 5a478bee46e88-2c1095f1d89mr255111eec.11.1773946800155; Thu, 19 Mar 2026 12:00:00 -0700 (PDT) X-Received: by 2002:a05:7300:f68c:b0:2c0:af67:e908 with SMTP id 5a478bee46e88-2c1095f1d89mr255082eec.11.1773946799136; Thu, 19 Mar 2026 11:59:59 -0700 (PDT) Received: from mail.broadcom.net ([192.19.144.250]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2c0e55834a0sm9607220eec.14.2026.03.19.11.59.57 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 19 Mar 2026 11:59:58 -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 v3 4/4] mmc: core: Add MMC pstore backend driver Date: Thu, 19 Mar 2026 14:57:05 -0400 Message-Id: <20260319185705.1516950-5-kamal.dasu@broadcom.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260319185705.1516950-1-kamal.dasu@broadcom.com> References: <20260319185705.1516950-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 96ea84948d76..2f8470c3c480 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18087,6 +18087,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..09f5734d2918 --- /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) + * @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 - 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 offset_bytes; + 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; + offset_bytes =3D part_sect_ofs * MMC_PSTORE_SECTOR_SIZE; + + 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 + * + * 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