SDIO驅動(5)sdio總線上的probe

Linux 2.6.38

sdio總線上driver和設備的match成功只是軟件之間的”切口“,好比《還錢》:

劉金山:明月幾時有?

馮鞏:擡頭自己瞅。

但是馮鞏並沒有馬上把請給他吧?這裏硬件的連通性、能不能工作還不知道,所以要probe探測一下。

static int sdio_bus_probe(struct device *dev)
{
	struct sdio_driver *drv = to_sdio_driver(dev->driver);
	struct sdio_func *func = dev_to_sdio_func(dev);
	const struct sdio_device_id *id;
	int ret;

	id = sdio_match_device(func, drv);
	if (!id)
		return -ENODEV;

	/* Unbound SDIO functions are always suspended.
	 * During probe, the function is set active and the usage count
	 * is incremented.  If the driver supports runtime PM,
	 * it should call pm_runtime_put_noidle() in its probe routine and
	 * pm_runtime_get_noresume() in its remove routine.
	 */
	if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD) {
		ret = pm_runtime_get_sync(dev);
		if (ret < 0)
			goto out;
	}

	/* Set the default block size so the driver is sure it's something
	 * sensible. */
	sdio_claim_host(func);
	ret = sdio_set_block_size(func, 0);
	sdio_release_host(func);
	if (ret)
		goto disable_runtimepm;

	ret = drv->probe(func, id);
	if (ret)
		goto disable_runtimepm;

	return 0;

disable_runtimepm:
	if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD)
		pm_runtime_put_noidle(dev);
out:
	return ret;
}
3、4行不用說了。

第8~10行,之前的match操作也調用的sdio_match_device,不過當時僅僅關心是不是匹配,其他一概不管。這裏需要用到它的sdio_device_id返回值。


第12~22行,sdio host的caps成員記錄其特性(capability),MMC_CAP_POWER_OFF_CARD是電源管理(Power Manager)方面的選項,設置了這個標誌的host在suspend的時候將把card的電關掉。很顯然,在probe的過程中不允許這種情況發生,解決方式就是禁止MMC子系統進入suspend模式。pm_runtime_get_sync()調用後將增加sdio device的引用計數,系統suspend過程中檢測這個引用計數,發現其不爲零就會退出suspend流程。相應的,pm_runtime_put_noidle()降低引用計數,也即它們必須成對使用。


第26~28行,設置block size的時候要做好臨街資源的保護和同步,sdio_claim_host獨佔式地(exclusively)佔用host,用完後調用sdio_release_host進行釋放。控制器作爲host,一個host連接的設備(MMC子系統稱作card)不止一個,所以做好同步是分所應當,比如進程1在使用host操作card a,在進程1工作未完成前進程2不得同時用該host訪問card b;當然,進程1用完後要及時地釋放。

獨佔訪問的實現:

void sdio_claim_host(struct sdio_func *func)
{
	mmc_claim_host(func->card->host);
}

static inline void mmc_claim_host(struct mmc_host *host)
{
	__mmc_claim_host(host, NULL);
}

/**
 *	__mmc_claim_host - exclusively claim a host
 *	@host: mmc host to claim
 *	@abort: whether or not the operation should be aborted
 *
 *	Claim a host for a set of operations.  If @abort is non null and
 *	dereference a non-zero value then this will return prematurely with
 *	that non-zero value without acquiring the lock.  Returns zero
 *	with the lock held otherwise.
 */
int __mmc_claim_host(struct mmc_host *host, atomic_t *abort)
{
	DECLARE_WAITQUEUE(wait, current);
	unsigned long flags;
	int stop;

	might_sleep();

	add_wait_queue(&host->wq, &wait);
	spin_lock_irqsave(&host->lock, flags);
	while (1) {
		set_current_state(TASK_UNINTERRUPTIBLE);
		stop = abort ? atomic_read(abort) : 0;
		if (stop || !host->claimed || host->claimer == current)
			break;
		spin_unlock_irqrestore(&host->lock, flags);
		schedule();
		spin_lock_irqsave(&host->lock, flags);
	}
	set_current_state(TASK_RUNNING);
	if (!stop) {
		host->claimed = 1;
		host->claimer = current;
		host->claim_cnt += 1;
	} else
		wake_up(&host->wq);
	spin_unlock_irqrestore(&host->lock, flags);
	remove_wait_queue(&host->wq, &wait);
	if (!stop)
		mmc_host_enable(host);
	return stop;
}
顯然幹事的是__mmc_claim_host,且其第二個參數爲NULL。

23行,DECLARE_WAITQUEUE是一個宏,我們把它展開:

#define DECLARE_WAITQUEUE(name, tsk)					\
	wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
#define __WAITQUEUE_INITIALIZER(name, tsk) {				\
	.private	= tsk,						\
	.func		= default_wake_function,			\
	.task_list	= { NULL, NULL } }

#define current (get_current())

DECLARE_WAITQUEUE(wait, current);
wait_queue_t wait = {
	.private = get_current(),
	.func		= default_wake_function,			\
	.task_list	= { NULL, NULL }
}

這裏創建一個名爲"wait"的等待隊列並對其成員初始化,設置其private成員爲當前進程。

27行,might_sleep()提醒使用代碼的人,這個函數可能會睡眠。如果在原子型上下文(spinlock, irq-handler, ...)調用,this macro will print a stack trace,當然開啓該功能需要打開相應的config。這裏不多說。

29行,add_wait_queue(&host->wq, &wait),把自己(“wait”)加入到等待隊列中(host->wq)。host->wq在SDIO驅動(2)Host註冊流程提到的mmc_alloc_host()函數中已經初始化。

30行,spin_lock_irqsave(&host->lock, flags),這個spinlock執行三個動作:保存當前中斷標誌(到flag),禁止CPU中斷,獲取lock;自然spin_unlock_irqrestore執行相反的操作。

31~39行之間的 while循環,首先設置進程狀態爲TASK_UNINTERRUPTIBLE不可中斷,進而對信號不做響應,保證當前進程在等待時不會受到干擾。傳進來的第二個參數爲NULL,所以stop等於0。接着進行判斷,如果我們獲取了host的使用權則結束循環,否則恢復中斷、釋放spinlock並進行調度,當前進程進入睡眠狀態等待喚醒。什麼情況下喚醒?另一個進程release host的時候:

static void mmc_do_release_host(struct mmc_host *host)
{
	unsigned long flags;

	spin_lock_irqsave(&host->lock, flags);
	if (--host->claim_cnt) {
		/* Release for nested claim */
		spin_unlock_irqrestore(&host->lock, flags);
	} else {
		host->claimed = 0;
		host->claimer = NULL;
		spin_unlock_irqrestore(&host->lock, flags);
		wake_up(&host->wq);
	}
}
很清楚了不是?

當前進程被喚醒, set_current_state(TASK_RUNNING)繼續運行。

41~45行,表明host被當前進程佔用了並增加計數。 

46行移除等待隊列,50行使能host。


現在我們已經擁有了host的所有權,調用sdio_set_block_size設置通信時一個block的大小,業內叫“block size”:

/**
 *	sdio_set_block_size - set the block size of an SDIO function
 *	@func: SDIO function to change
 *	@blksz: new block size or 0 to use the default.
 *
 *	The default block size is the largest supported by both the function
 *	and the host, with a maximum of 512 to ensure that arbitrarily sized
 *	data transfer use the optimal (least) number of commands.
 *
 *	A driver may call this to override the default block size set by the
 *	core. This can be used to set a block size greater than the maximum
 *	that reported by the card; it is the driver's responsibility to ensure
 *	it uses a value that the card supports.
 *
 *	Returns 0 on success, -EINVAL if the host does not support the
 *	requested block size, or -EIO (etc.) if one of the resultant FBR block
 *	size register writes failed.
 *
 */
int sdio_set_block_size(struct sdio_func *func, unsigned blksz)
{
	int ret;

	if (blksz > func->card->host->max_blk_size)
		return -EINVAL;

	if (blksz == 0) {
		blksz = min(func->max_blksize, func->card->host->max_blk_size);
		blksz = min(blksz, 512u);
	}

	ret = mmc_io_rw_direct(func->card, 1, 0,
		SDIO_FBR_BASE(func->num) + SDIO_FBR_BLKSIZE,
		blksz & 0xff, NULL);
	if (ret)
		return ret;
	ret = mmc_io_rw_direct(func->card, 1, 0,
		SDIO_FBR_BASE(func->num) + SDIO_FBR_BLKSIZE + 1,
		(blksz >> 8) & 0xff, NULL);
	if (ret)
		return ret;
	func->cur_blksize = blksz;
	return 0;
}

這裏有必要說一下struct sdio_func。一張SDIO卡可以支持很多Functions,一個Function對應軟件上的struct sdio_func結構體,最後反應到驅動模型上就是一個device:

int sdio_add_func(struct sdio_func *func)
{
	ret = device_add(&func->dev);

	return ret;
}
host通過CMD5獲取卡支持的Functions個數,數據保存在Response R4中:


struct sdio_func的card指針成員表示該設備屬於的card類型。所以24行,如果參數blksz大於host規定最大block size,返回EINVAL錯誤。

27~30行,第二段註釋說的很清楚,就是設置雙方(host和function device)都支持最大block size。

32行,重點來了mmc_io_rw_direct,我們參照它的參數分析:

int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn, unsigned addr, u8 in, u8 *out)
card,對應外接的SDIO卡,card的host成員代表該卡連接的host controller,host controller負責和卡通信的具體實現。

write,讀寫標誌,sdio的spec規定:1爲寫數據到sdio卡,0爲讀數據。

fn, function number,一個sdio卡支持不同的功能,這些功能經過編號後就是這裏的function number。function number用3個bit表示,即功能號最大爲7。

這裏fn=0,再次翻開spec:Note that function 0 selects the common I/O area (CIA)。CIA-Common I/O Area:


addr,訪問地址,佔17個bit。

/*
 * Function Basic Registers (FBR)
 */

#define SDIO_FBR_BASE(f)	((f) * 0x100) /* base of function f's FBRs */
#define SDIO_FBR_BLKSIZE	0x10	/* block size (2 bytes) */
CIA區域的FBR:

顯而易見,SDIO_FBR_BASE(func->num) + SDIO_FBR_BLKSIZE = 0x100+0x10 = 0x110,是FBR的block size寄存器的低8位地址,38行定義高8位地址。

in,寫到sdio卡中的數據,這裏分兩次寫入。

out,如果我們期望得到sdio卡的response,卡的當前狀態信息會被寫入out指向的位置。

經過以上兩次mmc_io_rw_direct()調用的寫入,通信雙方的block size就設置好了,至於數據怎麼打包、怎麼發送、如何獲取迴應數據參見 SDIO驅動(6)命令的構建和發送。設置好後,同步block size到42行的func->cur_blksize成員。


接着32行,調用sdio driver的probe函數,我們WiFi驅動(1)框架解析提到的RTL8723爲例:

static struct sdio_driver r871xs_drv = {
    .probe = rtw_drv_init,
    .remove = rtw_dev_remove,
    .name = (char*)DRV_NAME,
    .id_table = sdio_ids,
};

static int rtw_drv_entry(void)
{
    ret = sdio_register_driver(&r871xs_drv);
    return ret;
}
那麼這時rtw_drv_init函數會被調用。


最後33~42行,異常處理之前都提到過,沒出意外的話整個probe就結束了,OVER。

發佈了168 篇原創文章 · 獲贊 150 · 訪問量 56萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章