SDIO驱动(5)sdio总线上的probe

xiaoxiao2021-02-28  110

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。

转载请注明原文地址: https://www.6miu.com/read-72681.html

最新回复(0)