sdhci: Do not reprogram an unchanged clock

sdhci_set_clock() compared the requested clock with slot->clock, which holds
the rate that the host driver actually applied.  Some drivers do not apply
the requested rate: the RK3568 one maps the 400kHz identification frequency
to 375kHz.  The two values then never match and every update_ios call goes
through SDHCI_SET_CLOCK() again, even when the clock did not change.

That ends in clk_set_freq(), which takes a sleepable lock, from the done
context of the mmc probe with the CAM locks held.  With another thread
holding the clock topology lock this panics with "sleeping thread holds CAM
device lock".

Remember the last requested clock in a new field and compare against it.
Clear it wherever slot->clock is cleared so that a reset still forces an
update.

Tested on a Quartz64 Model A with an NVMe drive on the PCIe slot, where the
panic was seen: 5 cold boots out of 5 attached everything.

Reviewed by:	imp
Differential Revision:	https://reviews.freebsd.org/D61012
This commit is contained in:
Emmanuel Vadot committed 2026-11-05 15:50:42 +01:00
1 parent 40363144e8
commit 3e9ff26d2a
2 files changed
+5 -1

No files matched your search

+4 -1
View File
@@ -416,8 +416,9 @@ sdhci_set_clock(struct sdhci_slot *slot, uint32_t clock)
uint16_t div;
int timeout;
if (clock == slot->clock)
if (clock == slot->req_clock)
return;
slot->req_clock = clock;
clock = SDHCI_SET_CLOCK(slot->bus, slot, clock);
slot->clock = clock;
@@ -1275,11 +1276,13 @@ sdhci_generic_reset(device_t brdev __unused, struct sdhci_slot *slot,
/* This is to force an update */
clock = slot->clock;
slot->clock = 0;
slot->req_clock = 0;
sdhci_set_clock(slot, clock);
}
if (mask & SDHCI_RESET_ALL) {
slot->clock = 0;
slot->req_clock = 0;
slot->power = 0;
}
+1
View File
@@ -392,6 +392,7 @@ struct sdhci_slot {
uint32_t retune_ticks; /* Re-tuning callout ticks [hz] */
uint32_t intmask; /* Current interrupt mask */
uint32_t clock; /* Current clock freq. */
uint32_t req_clock; /* Last requested clock freq. */
size_t offset; /* Data buffer offset */
uint8_t hostctrl; /* Current host control register */
uint8_t retune_count; /* Controller re-tuning count [s] */