基於linux-2.6.38.8核心的wifi驅動分析
//參考給一些前輩們的文章
1、sdio介面層解析
SDIO匯流排
SDIO匯流排和USB匯流排類似,SDIO也有兩端,其中一端是HOST端,另一端是device端。所有的通訊都是由HOST端傳送命令開始的,Device端只要能解析命令,就可以相互通訊。
CLK訊號:HOST給DEVICE的時鐘訊號.每個時鐘週期傳輸一個命令或資料位。
CMD訊號:雙向的訊號,用於傳送命令和反應。
DAT0-DAT3 訊號:四條用於傳送的資料線。
VDD訊號:電源訊號。
VSS1,VSS2:電源地訊號。
nCD 用於檢測卡是否插入。
fs2410 mmc/sd的電路圖
SDIO熱插拔原理:
方法:設定一個定時器檢查或插拔中斷檢測
硬體:GPG10(EINT18)用於SD卡檢測
GPG10 為高電平 即沒有插入SD卡(參見fs2410原理圖/晶片手冊)
GPG10為低電平 即插入了SD卡
SDIO命令
SDIO總線上都是HOST端發起請求,然後DEVICE端迴應請求。其中請求和迴應中會資料資訊。sdio命令由6個位元組組成。
1. Command:用於開始傳輸的命令,是由HOST端發往DEVICE端的。其中命令是通過CMD訊號線傳送的。
2. Response:迴應是DEVICE返回的HOST的命令,作為Command的迴應。也是通過CMD線傳送的。
3. Data:資料是雙向的傳送的。可以設定為1線模式,也可以設定為4線模式。資料是通過DAT0-DAT3訊號線傳輸的。
SDIO的每次操作都是由HOST在CMD線上發起一個CMD,對於有的CMD,DEVICE需要返回Response,有的則不需要。
對於讀命令,首先HOST會向DEVICE傳送命令,緊接著DEVICE會返回一個握手訊號,此時,當HOST收到迴應的握手訊號後,會將資料放在4位的資料線上,在傳送資料的同時會跟隨著CRC校驗碼。當整個讀傳送完畢後,HOST會再次傳送一個命令,通知DEVICE操作完畢,DEVICE同時會返回一個響應。
對於寫命令,首先HOST會向DEVICE傳送命令,緊接著DEVICE會返回一個握手訊號,此時,當HOST收到迴應的握手訊號後,會將資料放在4位的資料線上,在傳送資料的同時會跟隨著CRC校驗碼。當整個寫傳送完畢後,HOST會再次傳送一個命令,通知DEVICE操作完畢,DEVICE同時會返回一個響應。
sd命令格式
以IO_SEND_OP_COND命令為例包含以下部分:
S(開始位) 總為0
D(方向位) 1 從host到 device (0 從device到host)
命令索引: 通過值000101B來
填充位 0
IO_OCR 運轉條件暫存器所支援的VDD的最小值和最大值
CRC7 7位CRC校驗資料
E(結束位) 總為1
MMC命令總共有40多個,分為class0 ~class7共8類,class0的所有卡必須支援。驅動程式通過傳送cmd1、cmd41來區分sd卡和mmc卡,如果傳送cmd1返回成功,則為mmc卡,否則傳送cmd41返回成功,則為sd卡。
cmd0 初始化mmc卡
Sdio介面驅動
首先我們來探討幾個重要的資料結構:該結果位於core核心層,主要用於核心層與主機驅動層的資料交換處理。/include/linux/mmc/host.h
struct mmc_host 用來描述卡控制器
struct mmc_card 用來描述卡
struct mmc_driver 用來描述 mmc 卡驅動
struct sdio_func 用來描述功能裝置
struct mmc_host_ops 用來描述卡控制器操作介面函式功能,用於從主機控制器層向 core 層註冊操作函式,從而將core 層與具體的主機控制器隔離。也就是說 core 要操作主機控制器,就用這個 ops 當中給的函式指標操作,不能直接呼叫具體主控制器的函式。
/drivers/mmc/host/s3cmci.c
static struct platform_driver s3cmci_driver = {
.driver = {
.name = "s3c-sdi", //名稱和平臺裝置定義中的對應
.owner = THIS_MODULE,
.pm = s3cmci_pm_ops,
},
.id_table = s3cmci_driver_ids,
.probe = s3cmci_probe, //平臺裝置探測介面函式
.remove = __devexit_p(s3cmci_remove),
.shutdown = s3cmci_shutdown,
};
s3cmci_probe(struct platform_device *pdev);
mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev); //分配mmc_host結構體
---->INIT_DELAYED_WORK(&host->detect, mmc_rescan); //初始化工作佇列
/*註冊中斷處理函式s3cmci_irq,來處理資料收發過程引起的各種中斷*/
request_irq(host->irq, s3cmci_irq, 0, DRIVER_NAME, host) //註冊中斷處理函式s3cmci_irq
/*註冊中斷處理s3cmci_irq_cd函式,來處理熱撥插引起的中斷,中斷觸發的形式為上升沿、下降沿觸發*/
request_irq(host->irq_cd, s3cmci_irq_cd,IRQF_TRIGGER_RISING |IRQF_TRIGGER_FALLING,
DRIVER_NAME, host)
mmc_add_host(mmc); //initialise host hardware 初始化host控制器
----> device_add(&host->class_dev); //新增裝置到mmc_bus_type總線上的裝置連結串列中
----> mmc_start_host(host); //啟動mmc host
/*MMC drivers should call this when they detect a card has been inserted or removed.檢測sd卡是否插上或移除*/
---->mmc_detect_change(host, 0);
/*Schedule delayed work in the MMC work queue.排程延時工作佇列*/
mmc_schedule_delayed_work(&host->detect, delay);
搜尋host->detected得到以下資訊:
/drivers/mmc/core/host.c
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
mmc_rescan(struct work_struct *work)
---->mmc_bus_put(host);//card 從bus上移除時,釋放它佔有的匯流排空間
/*判斷當前mmc host控制器是否被佔用,當前mmc控制器如果被佔用,那麼 host->claimed = 1;否則為0
*如果為1,那麼會在while(1)迴圈中呼叫schedule切換出自己,當佔用mmc控制器的操作完成之後,執行 *mmc_release_host()的時候,會啟用登記到等待佇列&host->wq中的其他程式獲得mmc主控制器的使用權
*/
mmc_claim_host(host);
mmc_rescan_try_freq(host, max(freqs[i], host->f_min);
/**/
static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq)
{
…………………………………………..
/* Order's important: probe SDIO, then SD, then MMC */
if (!mmc_attach_sdio(host))
return 0;
if (!mmc_attach_sd(host))
return 0;
if (!mmc_attach_mmc(host))
return 0;
………………………………………….
}
mmc_attach_sdio(struct mmc_host *host) //匹配sdio介面卡
--->mmc_attach_bus(host, &mmc_sdio_ops);
/*當card與總線上的驅動匹配,就初始化card*/
mmc_sdio_init_card(host, host->ocr, NULL, 0);
--->card = mmc_alloc_card(host, NULL);//分配一個card結構體
mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL); //設定mmc_bus的工作模式
struct sdio_func *sdio_func[SDIO_MAX_FUNCS]; //SDIO functions (devices)
sdio_init_func(host->card, i + 1);
--->func = sdio_alloc_func(card); //分配struct sdio_fun(sdio功能裝置)結構體
mmc_io_rw_direct();
card->sdio_func[fn - 1] = func;
mmc_add_card(host->card); //將具體的sdio裝置掛載到mmc_bus_types 匯流排
sdio_add_func(host->card->sdio_func[i]); //將sdio功能裝置掛載到sdio_bus_types匯流排
附:mmc_card結構體中sdio_func[]賦值為分配的sdio_fun結構體,通過fun結構體與wireless驅動層建立聯絡
--------------------------------------------------------------------------------
mmc_attach_sd(struct mmc_host *host) //匹配sd卡
---> mmc_sd_attach_bus_ops(host);
mmc_sd_init_card(host, host->ocr, NULL);//檢測、初始化sd卡
mmc_add_card(struct mmc_card *card) // Register a new MMC card with the driver model.以驅動模式註冊一個新mmc卡
mmc_claim_host(host);
mmc_attach_mmc(struct mmc_host *host) //匹配mmc卡
---> mmc_attach_bus_ops(host);
mmc_init_card(host, host->ocr, NULL);//檢測、初始化mmc
mmc_add_card(host->card);
mmc_claim_host(host);
struct mmc_request {
struct mmc_command *cmd;
struct mmc_data *data;
struct mmc_command *stop;
void *done_data; /* completion data */
void (*done)(struct mmc_request *);/* completion function */
};
/*將val寫到addr地址*/
#define writel(val, addr) outl((val), (unsigned long)(addr))
/*從addr地址處,獲取資訊*/
#define readl(addr) inl((unsigned long)(addr))
/*傳送請求*/
s3cmci_send_request(struct mmc_host *mmc)
---->struct mmc_request *mrq = host->mrq;
/* Clear command, data and fifo status registers
*Fifo clear only necessary on 2440, but doesn't hurt on 2410
*/清除相應狀態暫存器
writel(0xFFFFFFFF, host->base + S3C2410_SDICMDSTAT);
writel(0xFFFFFFFF, host->base + S3C2410_SDIDSTA);
writel(0xFFFFFFFF, host->base + S3C2410_SDIFSTA);
---->s3cmci_setup_data(host, cmd->data);
writel(0, host->base + S3C2410_SDIDCON); //寫SDIDCON暫存器
/* add to IMASK register */ 新增相應中斷
imsk = S3C2410_SDIIMSK_FIFOFAIL | S3C2410_SDIIMSK_DATACRC |
S3C2410_SDIIMSK_DATATIMEOUT | S3C2410_SDIIMSK_DATAFINISH;
enable_imask(host, imsk); //使能中斷
writel(0x0000FFFF, host->base + S3C2410_SDITIMER); //設定sditimer暫存器的狀態
writel(0xFF, host->base + S3C2410_SDIPRE); //設定sdipre SDI預分頻暫存器的狀態
---->if (s3cmci_host_usedma(host)) //判斷host的傳送模式:dma(直接記憶體訪問模式) 或 pio(CPU執行I/O埠指令來進行資料的讀寫的資料交換模式)
res = s3cmci_prepare_dma(host, cmd->data);
else
res = s3cmci_prepare_pio(host, cmd->data);
---->s3cmci_prepare_dma(struct s3cmci_host *host, struct mmc_data *data)
s3cmci_dma_setup(host, rw ? S3C2410_DMASRC_MEM : S3C2410_DMASRC_HW); //設定dma模式的屬性
s3c2410_dma_ctrl(unsigned int channel, enum s3c2410_chan_op op) //對dma傳輸模式的進行相關控制函式
s3c2410_dma_start(chan); //開始
s3c2410_dma_dostop(chan); //停止
s3c2410_dma_flush(chan); //重新整理
s3c2410_dma_started(chan);
---->s3cmci_prepare_pio(host, cmd->data);
do_pio_write(host);
enable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF); //使能Tx FIFO half interrupt 中斷
---------------------------------------------------------------------------------------
/*host控制器傳送命令*/
s3cmci_send_command(struct s3cmci_host *host,struct mmc_command *cmd)
---->writel(cmd->arg, host->base + S3C2410_SDICMDARG); //將命令寫到sdicarg暫存器中
writel(ccon, host->base + S3C2410_SDICMDCON); //將ccon寫到sdiccon(sdi控制暫存器)中
s3cmci_irq(int irq, void *dev_id) //設定sdi傳輸的相關中斷處理
---->mmc_signal_sdio_irq(host->mmc); //sdio 中斷訊號
wake_up_process(host->sdio_irq_thread); //喚醒
當插拔SDIO裝置,會觸發中斷通知到CPU,然後執行卡檢測中斷處理函式在這個中斷服務函式中,mmc_detect_change->mmc_schedule_delayed_work(&host->detect,delay), INIT_DELAYED_WORK(&host->detect, mmc_rescan)會排程mmc_rescan函式延時排程工作佇列,這樣也會觸發SDIO裝置的初始化流程,檢測到有效的SDIO裝置後,會將它註冊到系統中去。
static irqreturn_t s3cmci_irq_cd(int irq, void *dev_id)
{
struct s3cmci_host *host = (struct s3cmci_host *)dev_id;
........
mmc_detect_change(host->mmc, msecs_to_jiffies(500));
return IRQ_HANDLED;
}
2、wifi驅動解析
Drivers/net/wireless/libertas/if_sdio.c
Sdio裝置的驅動由sdio_driver結構體定義,sdio_register_driver函式將該裝置驅動掛載到sdio_bus_type總線上。
/* SDIO function device driver*/
struct sdio_driver {
char *name; //裝置名
const struct sdio_device_id *id_table; //裝置驅動ID
int (*probe)(struct sdio_func *, const struct sdio_device_id *);//匹配函式
void (*remove)(struct sdio_func *);
struct device_driver drv;
};
/*if_sdio.c*/
static struct sdio_driver if_sdio_driver = {
.name = "libertas_sdio",
.id_table = if_sdio_ids, //用於裝置與驅動的匹配
.probe = if_sdio_probe,
.remove = if_sdio_remove,
.drv = {
.pm = &if_sdio_pm_ops,
},
};
/**
* sdio_register_driver - register a function driver
* @drv: SDIO function driver
*/
int sdio_register_driver(struct sdio_driver *drv)
{
drv->drv.name = drv->name;
drv->drv.bus = &sdio_bus_type; //設定driver的bus為sdio_bus_type
return driver_register(&drv->drv);
}
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,
.pm = SDIO_PM_OPS_PTR,
};
注意:裝置或者驅動註冊到系統中的過程中,都會呼叫相應bus上的匹配函式來進行匹配合適的驅動或者裝置,對於sdio裝置的匹配是由sdio_bus_match和sdio_bus_probe函式來完成。
static int sdio_bus_match(struct device *dev, struct device_driver *drv)
{
struct sdio_func *func = dev_to_sdio_func(dev);
struct sdio_driver *sdrv = to_sdio_driver(drv);
if (sdio_match_device(func, sdrv))
return 1;
return 0;
}
static const struct sdio_device_id *sdio_match_device(struct sdio_func *func,
struct sdio_driver *sdrv)
{
const struct sdio_device_id *ids;
ids = sdrv->id_table;
if (sdio_match_one(func, ids))
return ids;
}
由以上匹配過程來看,通過匹配id_table 和 sdio_driver裝置驅動中id,來匹配合適的驅動或裝置。最終會呼叫.probe函式,來完成相關操作。
If_sdio_probe函式分析
Linux網路裝置驅動中的重要資料結構:struct net_device 和 struct net_device_ops
sdio_register_driver(&if_sdio_driver); //註冊sdio_driver結構體
card->workqueue = create_workqueue("libertas_sdio"); //建立工作佇列
INIT_WORK(&card->packet_worker, if_sdio_host_to_card_worker);
card->model == fw_table[i].model //檢測是否支援wifi卡
sdio_claim_host(func);
ret = sdio_enable_func(func); // enables a SDIO function for usage
使能sdio功能裝置
/* claim the IRQ for a SDIO function 註冊中斷*/
ret = sdio_claim_irq(func, if_sdio_interrupt);
priv = lbs_add_card(card, &func->dev);
----> wdev = lbs_cfg_alloc(dmdev); // 分配一個無線網路裝置結構體,並初始化
lbs_init_adapter(priv) // initialize adapter structure 初始化網路介面卡
dev = alloc_netdev(0, "wlan%d", ether_setup); //分配一個net_device結構體,並對其成員賦值
SET_NETDEV_DEV(dev, dmdev);
wdev->netdev = dev;
priv->dev = dev;
dev->netdev_ops = &lbs_netdev_ops;
init_waitqueue_head(&priv->waitq); //初始化等待佇列頭
priv->main_thread = kthread_run(lbs_thread, dev, "lbs_main");//建立核心執行緒lbs_thread,它的重要功能為:It handles all events generated by firmware, RX data received from firmware and TX data sent from kernel.即通過韌體產生收發事件,從韌體接受資料包,傳送資料包給kernel。
ret = lbs_start_card(priv);
/* gets the HW spec from the firmware and sets some basic parameters.*/
---->ret = lbs_setup_firmware(priv);// determined the firmware capabities.
設定fireware韌體的功能
lbs_cfg_register(priv)
----> ret = register_netdev(priv->dev);//註冊net_device網路裝置
priv->card = card;
priv->hw_host_to_card = if_sdio_host_to_card; //傳送資料
priv->enter_deep_sleep = if_sdio_enter_deep_sleep;
priv->exit_deep_sleep = if_sdio_exit_deep_sleep;
priv->reset_deep_sleep_wakeup = if_sdio_reset_deep_sleep_wakeup;
傳送資料包
if_sdio_host_to_card(struct lbs_private *priv,u8 type, u8 *buf, u16 nb)
----> packet = kzalloc(sizeof(struct if_sdio_packet) + size,GFP_ATOMIC);
struct if_sdio_packet {
struct if_sdio_packet *next;
u16 nb;
u8 buffer[0] __attribute__((aligned(4))); //緩衝區為4位元組對齊
};
memcpy(packet->buffer + 4, buf, nb); //複製buf的資料到packet
queue_work(card->workqueue, &card->packet_worker);
static const struct net_device_ops lbs_netdev_ops = {
.ndo_open = lbs_dev_open,
.ndo_stop = lbs_eth_stop,
.ndo_start_xmit = lbs_hard_start_xmit, //資料包傳送函式
.ndo_set_mac_address = lbs_set_mac_address,
.ndo_tx_timeout = lbs_tx_timeout,
.ndo_set_multicast_list = lbs_set_multicast_list,
.ndo_change_mtu = eth_change_mtu,
.ndo_validate_addr = eth_validate_addr,
};
lbs_hard_start_xmit()//檢測傳送條件 和 啟動資料包的傳送
----> netif_stop_queue(priv->mesh_dev); //當傳送佇列為滿或因其他原因來不及傳送當前上層傳送下來的資料包,則呼叫此函式阻止上層繼續向網路裝置驅動傳遞資料包。當資料包被髮送完成後Tx結束的中斷處理中,應該呼叫netif_wake_queue(priv->mesh_dev);喚醒被阻塞的上層,以啟動繼續向網路裝置驅動傳送資料包。
netdev_tx_t lbs_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
……………………………………
netif_stop_queue(priv->dev);
if (priv->tx_pending_len) {
/* This can happen if packets come in on the mesh and eth
device simultaneously -- there's no mutual exclusion on
hard_start_xmit() calls between devices. */
lbs_deb_tx("Packet on %s while busy\n", dev->name);
ret = NETDEV_TX_BUSY;
goto unlock;
}
…………………………….
txpd = (void *)priv->tx_pending_buf; //將傳送buf的地址賦給txpd
/*將要傳送的資料拷貝到priv->tx_pending_buf緩衝區*/
memcpy(&txpd[1], p802x_hdr, le16_to_cpu(txpd->tx_packet_length));
unlock:
wake_up(&priv->waitq); //喚醒等待佇列
}
由以上函式得知,當傳送佇列為滿或因其他原因來不及傳送當前上層傳遞下來的資料包,就會呼叫netif_stop_queue阻止上層繼續向網路裝置驅動傳送資料包。priv->tx_pending_len為skb中要傳送資料的長度,當它不為0時,就會呼叫wake_up(&priv->waitq)來喚醒等待佇列頭&priv->waitq。
搜尋priv->waitq得知,priv->waitq等待佇列頭(drivers\net\wireless\libertas\main.c)
init_waitqueue_head(&priv->waitq);
priv->main_thread = kthread_run(lbs_thread, dev, "lbs_main"); //建立核心執行緒lbs_thread
lbs_thread(void *data) //kernel thread
---->add_wait_queue(&priv->waitq, &wait); //將等待佇列新增到wait等待佇列中
/* Execute the next command */
if (!priv->dnld_sent && !priv->cur_cmd)
lbs_execute_next_command(priv);
priv->hw_host_to_card(priv, MVMS_DAT,priv->tx_pending_buf,priv->tx_pending_len);
注意:hw_host_to_card函式將帶txpd頭的packet通過sdio介面傳送到wifi晶片。搜尋priv->hw_host_to_card得到drivers\net\wireless\libertas\If_sdio.c中的
priv->hw_host_to_card = if_sdio_host_to_card;
if_sdio_host_to_card(struct lbs_private *priv,u8 type, u8 *buf, u16 nb)
----> packet = kzalloc(sizeof(struct if_sdio_packet) + size,GFP_ATOMIC); //分配套接字緩衝區
memcpy(packet->buffer + 4, buf, nb); //複製資料到網路緩衝區(skb)
queue_work(