正在研究mw8686 的Linux SDIO驱动,编译好后在s3c6410运行,发现未能出现网络接口设备。一查之下,它使用sdio总线进行操作驱动和设备,但是在 /sys/bus/sdio/devices 没有看到任何设备。因此推理是这个导致WiFi无法使用SDIO的原因。因为sdio驱动的初始化是放在probe当中,而probe是在sdio设备与sdio driver的match的过程中执行中。没有设备,意味着无法进行SDIO初始化。
我用的是Linux 2.6.28的版本,在网络上找到几个其它版本,主要Linux 2.6.21的,利用下午上课时间,进行一下课堂实验。发现这两个版本的实现差别很大,实际上两个分水岭。刚开始没明白这个道理,被两个版本的的源码搞得很混乱,因此为了解决问题,对这个两版本的SDIO实现进行分析
关于mw8686的模块分析,参见我的博文
首先根据档和源码来看三星发了几个关健的BSP,一个是针对linux 2.6.21.另外一个针对 linux 2.6.28的。这两个对SDIO的驱动采用不同的实际,有一篇官方文档可以看看,里面对Linux 2.6.21 SDIO实现分析得详细。
<<SMDK6400 6410 HS MMC porting Guide v1.0 >>,但是网上只能找到这一个
如果你对Linux 2.6的驱动模型相当了解,实际也能很快分析清楚。
SDIO总线
首先mw8686的if_sdio.c直接使用了sdio 总线。这个虚拟总线是由 driver/mmc/core/sdio_bus.c实现,在两个版本,这个基本是变化不大的地方。要注意,这里的SDIO和MMC是两个兼容的接口,因此在源码中两个术语经常互换.
sdio bus是一个标准的Linux 的总线,因此它需要有标准 bus device 和标准bus driver来注册到系统中。
这是 sdio_bus.c的总线定义
static struct bus_type sdio_bus_type = { .name = "sdio", .dev_attrs = sdio_dev_attrs, .match = sdio_bus_match, .uevent = sdio_bus_uevent, .probe = sdio_bus_probe, .remove = sdio_bus_remove, };
int sdio_register_bus(void) { return bus_register(&sdio_bus_type); }
void sdio_unregister_bus(void) { bus_unregister(&sdio_bus_type); } |
而对应总线驱动struct device_driver 是 struct sdio_driver
/* * SDIO function device driver */ struct sdio_driver { char *name; const struct sdio_device_id *id_table;
int (*probe)(struct sdio_func *, const struct sdio_device_id *); void (*remove)(struct sdio_func *);
struct device_driver drv; }; |
它使用 如下两个函数来注册和销毁sdio_driver.
extern int sdio_register_driver(struct sdio_driver *);
extern void sdio_unregister_driver(struct sdio_driver *);
而对应总线设备的就是 struct device 是 struct sdio_func,这个名字与device差别太大了,因此我一开没有太在意。
/* * SDIO function devices */ struct sdio_func { struct mmc_card *card; /* the card this device belongs to */ struct device dev; /* the device */ sdio_irq_handler_t *irq_handler; /* IRQ callback */ unsigned int num; /* function number */
unsigned char class; /* standard interface class */ unsigned short vendor; /* vendor id */ unsigned short device; /* device id */
unsigned max_blksize; /* maximum block size */ unsigned cur_blksize; /* current block size */
unsigned enable_timeout; /* max enable timeout in msec */
unsigned int state; /* function state */ #define SDIO_STATE_PRESENT (1<<0) /* present in sysfs */
u8 tmpbuf[4]; /* DMA:able scratch buffer */
unsigned num_info; /* number of info strings */ const char **info; /* info strings */
struct sdio_func_tuple *tuples; }; |
对于sdio_func操作
int sdio_add_func(struct sdio_func *func);
void sdio_remove_func(struct sdio_func *func);
在drivers/net/wireless/libertas/if_sdio.c
它定义了如下的 sdio_driver
static struct sdio_driver if_sdio_driver = { .name = "libertas_sdio", .id_table = if_sdio_ids, .probe = if_sdio_probe, .remove = if_sdio_remove, };
static int __init if_sdio_init_module(void) { int ret = 0; ret = sdio_register_driver(&if_sdio_driver); return ret; } static void __exit if_sdio_exit_module(void) { sdio_unregister_driver(&if_sdio_driver); }
|
SDIO采用的与MMC兼容的命令接口,因此对于mmc的操作,LINUX又封装一层,使用数据结构 mmc_host
struct mmc_host { struct device *parent; struct device class_dev; int index; const struct mmc_host_ops *ops; unsigned int f_min; unsigned int f_max; u32 ocr_avail; unsigned long caps; /* Host capabilities */ /* host specific block data */ unsigned int max_seg_size; /* see blk_queue_max_segment_size */ unsigned short max_hw_segs; /* see blk_queue_max_hw_segments */ unsigned short max_phys_segs; /* see blk_queue_max_phys_segments */ unsigned short unused; unsigned int max_req_size; /* maximum number of bytes in one req */ unsigned int max_blk_size; /* maximum size of one mmc block */ unsigned int max_blk_count; /* maximum number of blocks in one req */
/* private data */ spinlock_t lock; /* lock for claim and bus ops */
struct mmc_ios ios; /* current io bus settings */ u32 ocr; /* the current OCR setting */
/* group bitfields together to minimize padding */ unsigned int use_spi_crc:1; unsigned int claimed:1; /* host exclusively claimed */ unsigned int bus_dead:1; /* bus has been released */ #ifdef CONFIG_MMC_DEBUG unsigned int removed:1; /* host is being removed */ #endif
struct mmc_card *card; /* device attached to this host */
wait_queue_head_t wq;
struct delayed_work detect;
const struct mmc_bus_ops *bus_ops; /* current bus driver */ unsigned int bus_refs; /* reference counter */
unsigned int sdio_irqs; struct task_struct *sdio_irq_thread; atomic_t sdio_irq_thread_abort;
#ifdef CONFIG_LEDS_TRIGGERS struct led_trigger *led; /* activity led */ #endif
struct dentry *debugfs_root;
unsigned long private[0] ____cacheline_aligned; };
|
而mmc_host是一个特殊内核类 mmc_host,它会在 /sys/class/ 建立一个mmc_host类.
//drivers/mmc/core/host.c
static struct class mmc_host_class = { .name = "mmc_host", .dev_release = mmc_host_classdev_release, };
int mmc_register_host_class(void) { return class_register(&mmc_host_class); }
void mmc_unregister_host_class(void) { class_unregister(&mmc_host_class); } |
mmc_host用于描述MMC接口 CPU一侧的设备,它可以看成是class device的特例,而插入的SDIO设备,如SD卡则封装成数据结构,如果mmc_host发送命令和数据是通过 struct mmc_host_ops 操作
struct mmc_host_ops { void (*request)(struct mmc_host *host, struct mmc_request *req); void (*set_ios)(struct mmc_host *host, struct mmc_ios *ios); int (*get_ro)(struct mmc_host *host); int (*get_cd)(struct mmc_host *host); void (*enable_sdio_irq)(struct mmc_host *host, int enable); }; |
如果SDIO设备操作,直接采用如下定义
static struct mmc_host_ops s3c_hsmmc_ops = { .request = s3c_hsmmc_request, .set_ios = s3c_hsmmc_set_ios, }; |
mmc_card /mmc_bus
如果SD卡的之存储设备进行块设备操作,则需要另一个额外的mmc bus总线,并且需要定义mmc_card结构,对应(device).并且进行存储的块设备操作定义在mmc_driver类。
如果SDIO设备本身不需要这个总线
在mmc又单独定义一个 mmc总线,
static struct bus_type mmc_bus_type = { .name = "mmc", .dev_attrs = mmc_dev_attrs, .match = mmc_bus_match, .uevent = mmc_bus_uevent, .probe = mmc_bus_probe, .remove = mmc_bus_remove, .suspend = mmc_bus_suspend, .resume = mmc_bus_resume, };
int mmc_register_bus(void) { return bus_register(&mmc_bus_type); }
void mmc_unregister_bus(void) { bus_unregister(&mmc_bus_type); } |
mmc_card.它可以看一个bus device 设备
// include/linux/mmc/card.h
/* * MMC device */ struct mmc_card { struct mmc_host *host; /* the host this device belongs to */ struct device dev; /* the device */ unsigned int rca; /* relative card address of device */ unsigned int type; /* card type */ #define MMC_TYPE_MMC 0 /* MMC card */ #define MMC_TYPE_SD 1 /* SD card */ #define MMC_TYPE_SDIO 2 /* SDIO card */ unsigned int state; /* (our) card state */ #define MMC_STATE_PRESENT (1<<0) /* present in sysfs */ #define MMC_STATE_READONLY (1<<1) /* card is read-only */ #define MMC_STATE_HIGHSPEED (1<<2) /* card is in high speed mode */ #define MMC_STATE_BLOCKADDR (1<<3) /* card uses block-addressing */
u32 raw_cid[4]; /* raw card CID */ u32 raw_csd[4]; /* raw card CSD */ u32 raw_scr[2]; /* raw card SCR */ struct mmc_cid cid; /* card identification */ struct mmc_csd csd; /* card specific */ struct mmc_ext_csd ext_csd; /* mmc v4 extended card specific */ struct sd_scr scr; /* extra SD information */ struct sd_switch_caps sw_caps; /* switch (CMD6) caps */
unsigned int sdio_funcs; /* number of SDIO functions */ struct sdio_cccr cccr; /* common card info */ struct sdio_cis cis; /* common tuple info */ struct sdio_func *sdio_func[SDIO_MAX_FUNCS]; /* SDIO functions (devices) */ unsigned num_info; /* number of info strings */ const char **info; /* info strings */ struct sdio_func_tuple *tuples; /* unknown common tuples */
struct dentry *debugfs_root; }; |
mmc_card 与 sdio 总线的device (sdio_func)的交互的关系体现在 mmc_card中sdio_func定义
而mmc_card与mmc_host关系体现在定义 mmc_card的host定义中
而mmc_bus中,其驱动就是mmc_driver
/* * MMC device driver (e.g., Flash card, I/O card...) */ struct mmc_driver { struct device_driver drv; int (*probe)(struct mmc_card *); void (*remove)(struct mmc_card *); int (*suspend)(struct mmc_card *, pm_message_t); int (*resume)(struct mmc_card *); };
extern int mmc_register_driver(struct mmc_driver *); extern void mmc_unregister_driver(struct mmc_driver *) |
而使用了mmc_driver 好象只有处理SD卡块设备
//driver/mmc/core/block.c
static struct mmc_driver mmc_driver = { .drv = { .name = "mmcblk", }, .probe = mmc_blk_probe, .remove = mmc_blk_remove, .suspend = mmc_blk_suspend, .resume = mmc_blk_resume, }; |
它大体有如下关系,这里出问题就是BSP这一侧 sdio channel1的驱动
Linux 2.6.21的SDIO的实现
在Linu 2.6.21的SDIO驱动,它是除了是mmc_host的驱动外,还是一个Platform驱动.身兼两职.
在这个版本中,首先是定义三个platform_deivce 以注册到系统当中。但是它定义在
arch/arm/plat-s3c24xx/devs.c (这会让人感当相当迷惑),它创建是 s3c-hsmmc设备
struct platform_device s3c_device_hsmmc0 = { .name = "s3c-hsmmc", .id = 0, .num_resources = ARRAY_SIZE(s3c_hsmmc0_resource), .resource = s3c_hsmmc0_resource, .dev = { .platform_data = &s3c_hsmmc0_platform, } }; struct platform_device s3c_device_hsmmc1 = { .name = "s3c-hsmmc", .id = 1, .num_resources = ARRAY_SIZE(s3c_hsmmc1_resource), .resource = s3c_hsmmc1_resource, .dev = { .platform_data = &s3c_hsmmc1_platform, } };
|
其platform_driver 定义在drivers/mmc/s3c-hsmmc.c
static struct platform_driver s3c_hsmmc_driver = { .probe = s3c_hsmmc_probe, .remove = s3c_hsmmc_remove, .suspend = s3c_hsmmc_suspend, .resume = s3c_hsmmc_resume, .driver = { .name = "s3c-hsmmc", .owner = THIS_MODULE, }, }; static int __init s3c_hsmmc_drv_init(void) { return platform_driver_register(&s3c_hsmmc_driver); } static void __exit s3c_hsmmc_drv_exit(void) { platform_driver_unregister(&s3c_hsmmc_driver); }
|
在s3c_hsmmc_probe函数中,会进行一系列的初始化操作.
Linux 2.6.28的SDIO的实现
Linux 2.6.28又做了进一步调整,原来的s3c-hsmmc的设备和驱动已经放弃,原来在s3c-hsmmc.c 已经成为残余代码,虽然在内核中,但是不会加入Makefile编译。
其新的platform_device 定义分布定义在 dev-hsmmc.c /dev-hsmmc1.c / dev-hsmmc2.c,可以看到设备名字已经变成了 s3c-sdhci
struct s3c_sdhci_platdata s3c_hsmmc0_def_platdata = { .max_width = 4, .host_caps = (MMC_CAP_4_BIT_DATA | MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED), .cfg_ext_cd = setup_sdhci0_irq_cd, .detect_ext_cd = detect_sdhci0_irq_cd, .ext_cd = S3C_EINT(13), };
struct platform_device s3c_device_hsmmc0 = { .name = "s3c-sdhci", .id = 0, .num_resources = ARRAY_SIZE(s3c_hsmmc_resource), .resource = s3c_hsmmc_resource, .dev = { .dma_mask = &s3c_device_hsmmc_dmamask, .coherent_dma_mask = 0xffffffffUL, .platform_data = &s3c_hsmmc0_def_platdata, }, }; |
它会在系统初始化注册deivce.
而platform_driver定义在drivers/mmc/host/sdhci-s3c.c
//drivers/mmc/host/sdhci-s3c.c
static struct platform_driver sdhci_s3c_driver = { .probe = sdhci_s3c_probe, .remove = __devexit_p(sdhci_s3c_remove), .suspend = sdhci_s3c_suspend, .resume = sdhci_s3c_resume, .driver = { .owner = THIS_MODULE, .name = "s3c-sdhci", }, }; |
在测试WIFI模块时,在 /sys/bus/platform/devices中已经看到了 这两个device,但是在/sys/bus/sdio/deivces仍然没见到相应设备
[root@urbetter bin]# ls /sys/bus/platform/devices
dm9000_con201.0 s3c-keypad s3c-tvscaler s3c6400-uart.1
onenand s3c-lcd s3c-usbgadget s3c6400-uart.2
regulatory.0 s3c-mfc s3c-vpp s3c6400-uart.3
s3c-fimc.0 s3c-rotator s3c2410-ohci s3c6410-nand
s3c-fimc.1 s3c-sdhci.0 s3c2410-rtc s3c_otghcd
s3c-g2d s3c-sdhci.1 s3c2410-wdt sam-spi.0
s3c-g3d s3c-ts s3c2440-i2c sam-spi.1
s3c-jpeg s3c-tvenc s3c6400-uart.0 soc-audio |