Linux驅動——mmc card熱插拔檢測機制(十)【轉】

轉自:https://blog.csdn.net/u013836909/article/details/120913583

Linux驅動——mmc card熱插拔檢測機制(十)
備註:
  1. Kernel版本:5.4
  2. 使用工具:Source Insight 4.0
  3. 參考博客:
[sd card] mmc硬件總線掃描流程(以sd card爲例)

文章目錄
Linux驅動——mmc card熱插拔檢測機制(十)
前言
卡檢測時機
啓動host時,檢測
中斷檢測
輪詢檢測
如何掃描mmc硬件總線
detect task的創建
detect task的喚醒
detect task的實現
sd card熱插拔狀態的獲取
獲取sd card當前的插入狀態
通過GPIO獲取當前sd card的插入狀態
sd card熱插拔的實現
中斷監控
輪詢監控
前言
  掃描mmc硬件總線,也就是檢測mmc硬件總線上是否有掛載card。更加通俗的,就是卡槽上是否有插入card。
  檢測機制有如下兩種:

中斷檢測
輪詢檢測
卡檢測時機
  mmc core在如下情況下會去掃描mmc硬件總線:

啓動host時,調用_mmc_detect_change,喚醒host->detect;
cd-gpio中斷檢測到card狀態發送變化,調用mmc_detect_change,喚醒host->detect;
host要求輪詢sd card插入狀態的情況下,所進行的輪詢操作;
啓動host時,檢測
  當啓動一個host的時候,並不知道當前是否有card插入,此時需要調用mmc_detect_change來假設card插入狀態發生了變化,並且進行第一次掃描mmc硬件總線。

mmc_add_host——>mmc_start_host——>_mmc_detect_change
1
核心代碼如下:

void mmc_start_host(struct mmc_host *host)
{
......
//申請 cd_gpio
mmc_gpiod_request_cd_irq(host);

/* 到這裏host已經可以工作了,可以開始進行後續的card操作了 */
_mmc_detect_change(host, 0, false); // 調用mmc_detect_change檢測card變化
}

中斷檢測
  底層硬件發現card插入狀態發生變化而調用mmc_detect_change的時候(sd card插入狀態監控)。
  當底層硬件發現card插入狀態發生變化,例如sd card的插入或者拔出可以觸發某個GPIO產生中斷。
  此時,可以在中斷處理中調用mmc_detect_change來進行掃描mmc硬件總線,並且根據總線上的card狀態變化進行處理。
這種情況的主要目的是爲了實現sd card的熱插拔。

static irqreturn_t mmc_gpio_cd_irqt(int irq, void *dev_id)
{
/* Schedule a card detection after a debounce timeout */
struct mmc_host *host = dev_id;
struct mmc_gpio *ctx = host->slot.handler_priv;

host->trigger_card_event = true;

// 調用mmc_detect_change對card的插入狀態變化進行處理
// 注意,這裏ctx->cd_debounce_delay_ms=200,延時了200ms是用來進行消抖
mmc_detect_change(host, msecs_to_jiffies(ctx->cd_debounce_delay_ms));

return IRQ_HANDLED;
}

輪詢檢測
  host要求輪詢sd card插入狀態的情況下,所進行的輪詢操作(sd card插入狀態監控)。

  一般來說,在host無法根據硬件來及時獲取card插入狀態發生變化的情況下,會要求mmc_core每隔一段時間(一般是HZ,一秒)掃描一次mmc硬件總線。

  在這種情況下,mmc_host的MMC_CAP_NEEDS_POLL屬性會被設置。
  這種情況的主要目的也是爲了實現sd card的熱插拔。

void mmc_rescan(struct work_struct *work)
{
struct mmc_host *host =
container_of(work, struct mmc_host, detect.work);
int i;

......

out:
// 當host設置了MMC_CAP_NEEDS_POLL屬性時,需要每隔HZ的時間輪詢檢測host的卡槽狀態,
// 調度了host->detect工作,對應就是mmc_rescan
// INIT_DELAYED_WORK(&host->detect, mmc_rescan)
// 這樣,通過mmc_schedule_delayed_work(&host->detect, HZ)就會每隔HZ時間就會執行一次mmc_rescan
if (host->caps & MMC_CAP_NEEDS_POLL)
mmc_schedule_delayed_work(&host->detect, HZ);
}

如何掃描mmc硬件總線
從上述,我們知道了可以通過調用mmc_detect_change和執行host->detect工作來發起mmc硬件總線的掃描。而mmc_detect_change最終也是執行mmc_host->detect工作來發起mmc硬件總線掃描的。

detect task的創建
struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
{
......
// 初始化detect工作爲mmc_rescan,
// 後續調度host->detect來檢測是否有card插入時,
// 就會調用到mmc_rescan
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
......
}

detect task的喚醒
mmc_detect_change:

void mmc_detect_change(struct mmc_host *host, unsigned long delay)
{
_mmc_detect_change(host, delay, true);
}
EXPORT_SYMBOL(mmc_detect_change);


_mmc_detect_change:

void _mmc_detect_change(struct mmc_host *host, unsigned long delay, bool cd_irq)
{
/*
* If the device is configured as wakeup, we prevent a new sleep for
* 5 s to give provision for user space to consume the event.
*/
if (cd_irq && !(host->caps & MMC_CAP_NEEDS_POLL) &&
device_can_wakeup(mmc_dev(host)))
pm_wakeup_event(mmc_dev(host), 5000);

host->detect_change = 1; // 檢測到card狀態發生變化的標識
mmc_schedule_delayed_work(&host->detect, delay); // 間隔delay jiffies之後調用host->detect的工作(mmc_rescan)
}

detect task的實現
  首先說明,當mmc core檢測到一張card並且初始化該card完成時,會將該mmc_bus於該card綁定,相應的mmc_bus的操作mmc_bus_ops就會被設置成對應card類型的操作集。

  例如emmc的話就是mmc_ops,sd card的話就是mmc_sd_ops,sdio card就是mmc_sdio_ops。

  所以mmc_rescan主要是進行以下兩種操作:

當mmc_host的mmc_bus_ops沒有被設置的時候
  這種情況下,說明mmc_bus並沒有和card綁定。也就是之前並沒有card插入或者識別初始化card的過程中失敗了。
  所以只需要去判斷當前是否有card插入,如果有的話,進行card的識別和初始化操作(並且會設置mmc_bus_ops)。否則,說明都不做。

當mmc_host的mmc_bus_ops被設置的時候
  這種情況下,說明mmc_bus已經和card綁定了。也就是之前已經有card插入並且初始化成功了。
  那麼此時,需要調用mmc_bus_ops中的detect方法(對於sd card來說就是mmc_sd_detect)來判斷card是否被移除,並且進行相應的動作。

void mmc_rescan(struct work_struct *work)
{
struct mmc_host *host =
container_of(work, struct mmc_host, detect.work);
int i;

/* 如果rescan_disable被設置,說明host此時還禁止rescan */
if (host->rescan_disable)
return;

/* If there is a non-removable card registered, only scan once */
/* 對於設備不可移除的host來說,只能rescan一次 */
if (!mmc_card_is_removable(host) && host->rescan_entered)
return;
host->rescan_entered = 1;

if (host->trigger_card_event && host->ops->card_event) {
mmc_claim_host(host);
host->ops->card_event(host);
mmc_release_host(host);
host->trigger_card_event = false;
}

mmc_bus_get(host);// 獲取host對應的bus

/* Verify a registered card to be functional, else remove it. */
if (host->bus_ops && !host->bus_dead)
host->bus_ops->detect(host);

host->detect_change = 0;

/*
* Let mmc_bus_put() free the bus/bus_ops if we've found that
* the card is no longer present.
*/
mmc_bus_put(host);
// 因爲在這個函數的前面已經獲取了一次host,
// 可能導致host->bus_ops->detect中檢測到card拔出之後,
// 沒有真正釋放到host的bus,所以這裏先put一次
// host bus的計數(bus_refs)爲0的時候,會調用__mmc_release_bus清空host bus的信息
mmc_bus_get(host);

/* if there still is a card present, stop here */
if (host->bus_ops != NULL) {
mmc_bus_put(host);
goto out;
}

/*
* Only we can add a new handler, so it's safe to
* release the lock here.
*/
mmc_bus_put(host);

mmc_claim_host(host);
if (mmc_card_is_removable(host) && host->ops->get_cd &&
host->ops->get_cd(host) == 0) {
mmc_power_off(host);
mmc_release_host(host);
goto out;
}

// 調用mmc_rescan_try_freq,以支持的最低頻率作爲工作頻率嘗試搜索card
for (i = 0; i < ARRAY_SIZE(freqs); i++) {
if (!mmc_rescan_try_freq(host, max(freqs[i], host->f_min)))
break;
if (freqs[i] <= host->f_min)
break;
}
mmc_release_host(host);

out:
// 當host設置了MMC_CAP_NEEDS_POLL屬性時,需要每隔HZ的時間輪詢檢測host的卡槽狀態,
// 調度了host->detect工作,對應就是mmc_rescan
if (host->caps & MMC_CAP_NEEDS_POLL)
mmc_schedule_delayed_work(&host->detect, HZ);
}

sd card熱插拔狀態的獲取
獲取sd card當前的插入狀態
一般來說有兩種方式來獲取到sd card當前的插入狀態
(1)GPIO獲取的方法
可以通過sd card的card detect引腳來判斷當前是否有sd card插入
(2)host寄存器獲取的方法
某些host在硬件上有識別sd card是否插入的能力。這種情況下,可以通過讀取host的寄存器來獲取到當前是否有sd card插入。

通過GPIO獲取當前sd card的插入狀態
  以TF card爲例,卡座功能管腳定義:


可以觀察到,雖然TF CARD(micro)只有8個引腳,但是卡座另外定義了一個CD引腳(DET_SWITCH)。
我的猜想是,對於某些tf card卡座來說,當tf card插入時,會把CD引腳(DET_SWITCH)直接定義到連接到groud上去。
所以,這裏可以梳理出,

當card沒有插入的情況下,由於外部上拉的關係,cpu連接到DET_SWITCH的gpio爲高電平。

當card插入時,tf card卡座會把DET_SWITCH拉低,相應的,cpu連接到DET_SWITCH的gpio爲低電平。

當然,上述只是一種情況,具體cd gpio的電平情況取決於使用的卡座的實現。

註冊card插入狀態檢測GPIO軟件介紹
  mmc core中的mmc_start_host調用mmc_gpiod_request_cd_irq來爲host定義自己的cd-detect引腳,也就是對應上面連接到卡座上的CD引腳的GPIO。
mmc_gpiod_request_cd_irq具體實現如下:

void mmc_gpiod_request_cd_irq(struct mmc_host *host)
{
// struct mmc_gpio用來表示連接卡槽的一些GPIO的狀態
struct mmc_gpio *ctx = host->slot.handler_priv;
int irq = -EINVAL;
int ret;

if (host->slot.cd_irq >= 0 || !ctx || !ctx->cd_gpio)
return;

/*
* Do not use IRQ if the platform prefers to poll, e.g., because that
* IRQ number is already used by another unit and cannot be shared.
*/
// cd-gpio 不支持中斷功能時,使用輪詢檢測,不申請gpio irq
if (!(host->caps & MMC_CAP_NEEDS_POLL))
irq = gpiod_to_irq(ctx->cd_gpio);

if (irq >= 0) {
if (!ctx->cd_gpio_isr)
ctx->cd_gpio_isr = mmc_gpio_cd_irqt;
ret = devm_request_threaded_irq(host->parent, irq,
NULL, ctx->cd_gpio_isr,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
ctx->cd_label, host);
if (ret < 0)
irq = ret;
}

host->slot.cd_irq = irq;

if (irq < 0)
host->caps |= MMC_CAP_NEEDS_POLL;
}

所以後續就可以通過讀取 mmc_host->slot.handler_priv->cd_gpio的電平狀態來判斷當前card的插入狀態。

1. 通過GPIO獲取當前sd card的插入狀態

  在上述通過mmc_gpiod_request_cd_irq註冊完成cd gpio之後,就可以通過mmc_gpio_get_cd來獲取card的插入狀態。當其返回1時,表示當前有card插入,當其返回0是,表示當前沒有card插入。
其實現如下:

int mmc_gpio_get_cd(struct mmc_host *host)
{
struct mmc_gpio *ctx = host->slot.handler_priv; // 提取出host對應的struct mmc_gpio
int cansleep;

if (!ctx || !ctx->cd_gpio) // 如果沒有註冊cd gpio的情況下,直接返回了
return -ENOSYS;

cansleep = gpiod_cansleep(ctx->cd_gpio);
if (ctx->override_cd_active_level) {
int value = cansleep ?
gpiod_get_raw_value_cansleep(ctx->cd_gpio) :
gpiod_get_raw_value(ctx->cd_gpio);
return !value ^ !!(host->caps2 & MMC_CAP2_CD_ACTIVE_HIGH);
}

// 因爲mmc_gpio_get_cd返回1表示有card插入,返回0是表示沒有card插入
// MMC_CAP2_CD_ACTIVE_HIGH屬性被設置的話,表示當有card插入時爲cd gpio爲高電平(主要取決於卡座)
// 假設這裏MMC_CAP2_CD_ACTIVE_HIGH沒有被設置,也就是有card插入時cd gpio爲低電平
// 當cd gpio檢測到低電平的時候,相當於是!0 ^ 0=1,因此會返回1,有card插入
// 當cd gpio檢測到高電平的時候,相當於是!1 ^ 0=0,因此會返回0,沒有card插入
return cansleep ?
gpiod_get_value_cansleep(ctx->cd_gpio) :
gpiod_get_value(ctx->cd_gpio);
}
EXPORT_SYMBOL(mmc_gpio_get_cd);

2. 通過host寄存器獲取當前card插入狀態

  前提是某些host在硬件上有識別sd card是否插入的能力。這種情況下,可以通過讀取host的寄存器來獲取到當前是否有sd card插入。

以sdhci類host爲例:

static int sdhci_get_cd(struct mmc_host *mmc)
{
struct sdhci_host *host = mmc_priv(mmc);

// 通過調用mmc_gpio_get_cd,通過cd gpio獲取當前的card插入狀態,對應上述“通過GPIO獲取當前sd card的插入狀態”
int gpio_cd = mmc_gpio_get_cd(mmc);

if (host->flags & SDHCI_DEVICE_DEAD)
return 0;

/* If nonremovable, assume that the card is always present. */
// 對於不可移除的設備,總是返回1
if (!mmc_card_is_removable(host->mmc))
return 1;

/*
* Try slot gpio detect, if defined it take precedence
* over build in controller functionality
*/
// mmc_gpio_get_cd返回值有效的話,返回插入狀態
if (gpio_cd >= 0)
return !!gpio_cd;

/* If polling, assume that the card is always present. */

// 因爲host並不一定有通過mmc_gpio_request_cd註冊cd gpio,也就是沒有實現“通過GPIO獲取當前sd card的插入狀態”
// 這種情況下就嘗試通過用sdhci的寄存器來獲取


// 當SDHCI_QUIRK_BROKEN_CARD_DETECTION被設置的時候,說明該host並沒有提供檢測card插入狀態檢測的能力,
// 直接返回1,假設插入,由後續協議的工作來嘗試初始化
if (host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION)
return 1;

/* Host native card detect */

// 讀取sdhci的SDHCI_PRESENT_STATE的bit SDHCI_CARD_PRESENT來獲取card的插入狀態。
return !!(sdhci_readl(host, SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT);
}

sd card熱插拔的實現
  sd card熱插拔實現,就是對sd card的插入狀態的監控。通常來說有兩種方式中斷監控和輪詢監控。mmc core會選擇其中的一種方式來監控sd card的插入狀態。

中斷監控
  通過sd card的cd引腳的電平變化來觸發中斷,從而告知cpu說sd card的插入狀態已經發生了變化。實現方式如下:

cd-gpio的解析:
在dts中的定義如下:

&mmc0 {
// cd-gpios這個屬性名的定義取決於host driver將cd gpio定義成了什麼名字
// pio,也就是要使用的GPIO所使用的gpio controller
// 2:sd card的cd引腳所連接的pin_group
// 1:sd card的cd引腳所連接的pin_num
//GPIO_ACTIVE_LOW:取決於host driver如何解釋這個flag的,一般來說,GPIO_ACTIVE_LOW表示低電平有card插入,GPIO_ACTIVE_HIGH則表示高電平有card插入
cd-gpios = <&pio 2 1 GPIO_ACTIVE_LOW>; /* PC1*/
};

在mmc host將對cd-gpios進行解析,具體實現方式如下:

int mmc_of_parse(struct mmc_host *host)
{
struct device *dev = host->parent;

......

/* Parse Card Detection */
// 獲取卡不可移除標誌
if (device_property_read_bool(dev, "non-removable")) {
host->caps |= MMC_CAP_NONREMOVABLE;
} else {
cd_cap_invert = device_property_read_bool(dev, "cd-inverted");

if (device_property_read_u32(dev, "cd-debounce-delay-ms",
&cd_debounce_delay_ms))
cd_debounce_delay_ms = 200;

// 獲取輪詢查詢卡狀態標誌
if (device_property_read_bool(dev, "broken-cd"))
host->caps |= MMC_CAP_NEEDS_POLL;

// 申請cd-gpio管腳
ret = mmc_gpiod_request_cd(host, "cd", 0, false,
cd_debounce_delay_ms * 1000,
&cd_gpio_invert);
if (!ret)
dev_info(host->parent, "Got CD GPIO\n");
else if (ret != -ENOENT && ret != -ENOSYS)
return ret;

/*
* There are two ways to flag that the CD line is inverted:
* through the cd-inverted flag and by the GPIO line itself
* being inverted from the GPIO subsystem. This is a leftover
* from the times when the GPIO subsystem did not make it
* possible to flag a line as inverted.
*
* If the capability on the host AND the GPIO line are
* both inverted, the end result is that the CD line is
* not inverted.
*/
if (cd_cap_invert ^ cd_gpio_invert)
host->caps2 |= MMC_CAP2_CD_ACTIVE_HIGH;
}

.......
}

int mmc_gpiod_request_cd(struct mmc_host *host, const char *con_id,
unsigned int idx, bool override_active_level,
unsigned int debounce, bool *gpio_invert)
{
struct mmc_gpio *ctx = host->slot.handler_priv;
struct gpio_desc *desc;
int ret;

// 申請cd-gpio,並將配置爲輸入方向
desc = devm_gpiod_get_index(host->parent, con_id, idx, GPIOD_IN);
if (IS_ERR(desc))
return PTR_ERR(desc);

// 若支持硬件防抖,則配置
if (debounce) {
ret = gpiod_set_debounce(desc, debounce);
if (ret < 0)
ctx->cd_debounce_delay_ms = debounce / 1000;
}

// 獲取有效電平是否需要反轉
if (gpio_invert)
*gpio_invert = !gpiod_is_active_low(desc);

ctx->override_cd_active_level = override_active_level;
ctx->cd_gpio = desc;

return 0;
}
EXPORT_SYMBOL(mmc_gpiod_request_cd);

bool mmc_can_gpio_cd(struct mmc_host *host)
{
struct mmc_gpio *ctx = host->slot.handler_priv;

return ctx->cd_gpio ? true : false;
}
EXPORT_SYMBOL(mmc_can_gpio_cd);

註冊cd-gpio中斷引腳:

void mmc_gpiod_request_cd_irq(struct mmc_host *host)
{
// struct mmc_gpio用來表示連接卡槽的一些GPIO的狀態
struct mmc_gpio *ctx = host->slot.handler_priv;
int irq = -EINVAL;
int ret;

if (host->slot.cd_irq >= 0 || !ctx || !ctx->cd_gpio)
return;

/*
* Do not use IRQ if the platform prefers to poll, e.g., because that
* IRQ number is already used by another unit and cannot be shared.
*/
// cd-gpio 不支持中斷功能時,使用輪詢檢測,不申請gpio irq
if (!(host->caps & MMC_CAP_NEEDS_POLL))
irq = gpiod_to_irq(ctx->cd_gpio);

if (irq >= 0) {
if (!ctx->cd_gpio_isr)
ctx->cd_gpio_isr = mmc_gpio_cd_irqt;
ret = devm_request_threaded_irq(host->parent, irq,
NULL, ctx->cd_gpio_isr,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
ctx->cd_label, host);
if (ret < 0)
irq = ret;
}

host->slot.cd_irq = irq;

// 如果設置成中斷監控的方式失敗的情況下,設置輪詢標識MMC_CAP_NEEDS_POLL,使能輪詢監控
if (irq < 0)
host->caps |= MMC_CAP_NEEDS_POLL;
}

中斷監控的中斷處理函數mmc_gpio_cd_irqt:

static irqreturn_t mmc_gpio_cd_irqt(int irq, void *dev_id)
{
/* Schedule a card detection after a debounce timeout */
struct mmc_host *host = dev_id;
struct mmc_gpio *ctx = host->slot.handler_priv;

host->trigger_card_event = true;

// 調用mmc_detect_change對card的插入狀態變化進行處理
// 注意,這裏ctx->cd_debounce_delay_ms=200,延時了200ms是用來進行消抖
mmc_detect_change(host, msecs_to_jiffies(ctx->cd_debounce_delay_ms));

return IRQ_HANDLED;
}

輪詢監控
主要實現爲每隔一段時間(一般是HZ,1s)掃描一下mmc硬件總線。

對應需要設置的屬性標識
需要設置mmc_host的MMC_CAP_NEEDS_POLL屬性標識實現方式
dts的配置:
&mmc0 {
broken-cd;

// cd-gpios這個屬性名的定義取決於host driver將cd gpio定義成了什麼名字
// pio,也就是要使用的GPIO所使用的gpio controller
// 2:sd card的cd引腳所連接的pin_group
// 1:sd card的cd引腳所連接的pin_num
//GPIO_ACTIVE_LOW:取決於host driver如何解釋這個flag的,一般來說,GPIO_ACTIVE_LOW表示低電平有card插入,GPIO_ACTIVE_HIGH則表示高電平有card插入
cd-gpios = <&pio 2 1 GPIO_ACTIVE_LOW>; /* PC1*/
};

在mmc host將對broken-cd的解析同上。
實現方式:
每個1s中調用一次mmc_host->detect工作。

void mmc_rescan(struct work_struct *work)
{
struct mmc_host *host =
container_of(work, struct mmc_host, detect.work);
int i;

......
out:
// 當host設置了MMC_CAP_NEEDS_POLL屬性時,需要每隔HZ的時間輪詢檢測host的卡槽狀態,
// 調度了host->detect工作,對應就是mmc_rescan
if (host->caps & MMC_CAP_NEEDS_POLL)
mmc_schedule_delayed_work(&host->detect, HZ);
}

  在host啓動的時候,會執行一次mmc_host->detect工作,這時就開始進行輪詢了。
  以上就實現了每隔1s執行一次mmc_rescan來掃描mmc硬件總線的變化情況。
————————————————
版權聲明:本文爲CSDN博主「楓瀟瀟」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/u013836909/article/details/120913583

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章