分析了兩天的I2C驅動,發現每次解決一個問題的時候都會帶來新的問題,當大致讀完MMA7660驅動程序的時候發現,作爲一個字符設備I2C驅動,並不存在有open,close等接口,而我們知道,在Linux的世界裏設備即文件,也就是操作設備就相當於讀寫文件,而在一個簡單的字符設備裏總會實現一個file_operation的結構體以實現用戶層的調用,那麼當我們打開一個I2C設備的時候open在哪裏呢?
從框架開始說起,在網上尋找I2C框架的時候,我在很多地方看到一張圖
既然大家都喜歡拿圖說事,那麼我也嘗試着拿這個圖說事,也許會有偏差和錯誤,所以還望看到這博客的各位指正。首先我們要知道,我們寫的驅動(或者說移植的驅動,其實我目前做的也是移植)到底屬於那一層呢?答案是圖中的driver驅動層,但爲什麼呢?硬件實驗控制層所說的硬件控制指的是Soc上I2C控制器的控制,而這一部分一般是Soc生產商做好了的,畢竟他們最懂Soc上I2C應該怎麼跑,而我們只要把連接在Soc的I2C外設的一些特性告訴硬件控制代碼,它就能根據需要讓外設工作起來。
回到這張圖,很明顯可以知道用戶程序最開始接觸到的是client,也就是之前我說到的設備,其實這個設備是被封裝過的,封裝之後很合適的掛載在內核虛擬的I2C總線上,並且和同樣封裝過的i2c_driver進行綁定(具體可以查看昨天提到的這兩個結構體http://blog.csdn.net/ouchao0727/article/details/50327447,這兩個結構體相互保存了彼此的信息),在Linux下操作一個設備,都是在/dev目錄下打開對應的設備,前兩天的內容雖然創建了client,但client->dev中並沒有內容,這個時候用戶還沒辦法操作設備,於是probe幫我們做了這麼一件事(在MMA中probe函數內容很多,暫時移除一些和I2C框架沒什麼關係的內容和一些對理解I2C框架不重要錯誤處理,更方便閱讀)
static int mma7660_probe(struct i2c_client *client,const struct i2c_device_id *id)
{
int result;
struct input_dev *idev;
struct i2c_adapter *adapter;
struct mma7660_data_s* data = &mma7660_data;
mma7660_i2c_client = client;
adapter = to_i2c_adapter(client->dev.parent);
result = i2c_check_functionality(adapter,I2C_FUNC_SMBUS_BYTE |I2C_FUNC_SMBUS_BYTE_DATA);
assert(result);
hwmon_dev = hwmon_device_register(&client->dev);
mma7660_idev = input_allocate_polled_device();
mma7660_idev->poll = mma7660_dev_poll;
mma7660_idev->poll_interval = POLL_INTERVAL;
mma7660_idev->poll_interval_max = POLL_INTERVAL_MAX;
idev = mma7660_idev->input;
idev->name = MMA7660_DRV_NAME;
idev->id.bustype = BUS_I2C;
idev->evbit[0] = BIT_MASK(EV_ABS);
mutex_init(&data->interval_mutex);
mutex_init(&data->init_mutex);
input_set_abs_params(idev, ABS_X, -512, 512, INPUT_FUZZ, INPUT_FLAT);
input_set_abs_params(idev, ABS_Y, -512, 512, INPUT_FUZZ, INPUT_FLAT);
input_set_abs_params(idev, ABS_Z, -512, 512, INPUT_FUZZ, INPUT_FLAT);
result = input_register_polled_device(mma7660_idev);
mma7660_idev->input->close(mma7660_idev->input);
result = sysfs_create_group(&mma7660_idev->input->dev.kobj, &mma7660_attribute_group);
data->client = client;
data->pollDev = mma7660_idev;
i2c_set_clientdata(client, data);
return result;
}
代碼中有一句hwmon_dev
= hwmon_device_register(&client->dev);將client->dev註冊成一個hwmon設備(G-sensor屬於hardware monitor設備類),這樣會在sys/class目錄下創建文件夾,可以暴露給用戶空間(暫時不做過多分析)。然後便是mma7660_idev = input_allocated_polled_device();自動創建一個輪詢設備,並在之後把輪詢函數mma7660_dev_poll註冊進了mma7660_idev,然後又對一個輸入子設備idev進行一些初始化工作並嵌入到了mma_idev->input(這些都是指針操作,只要傳一個地址就行了),最後將mma7660_idev作爲一個設備註冊進內核,這個時候,這樣一個輪詢的輸入設備就被創建完畢了,input->open,input->close都在這裏實現了。int input_register_polled_device(struct input_polled_dev *dev)
{
struct input_dev *input = dev->input;
int error;
input_set_drvdata(input, dev);
INIT_DELAYED_WORK(&dev->work, input_polled_device_work);
if (!dev->poll_interval)
dev->poll_interval = 500;
if (!dev->poll_interval_max)
dev->poll_interval_max = dev->poll_interval;
input->open = input_open_polled_device;
input->close = input_close_polled_device;
error = input_register_device(input);
if (error)
return error;
error = sysfs_create_group(&input->dev.kobj,
&input_polldev_attribute_group);
if (error) {
input_unregister_device(input);
return error;
}
input_get_device(input);
return 0;
}
最後再利用i2c_set_clientdata(client,data)將這個輸入設備和client關聯起來,現在從上層open到client再到driver都已經聯繫了起來,但實際操作的硬件的部分呢?,其實這一個部分也與client有關,在client下有一個adapter的結構體,而adapter裏包含struct
i2c_algorithm *algostruct i2c_algorithm {
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
u32 (*functionality) (struct i2c_adapter *);
};
其實這個結構體裏已經保存有操作硬件的函數指針,這裏有一個遺憾,我始終沒能很好的找出這幾個回調函數是在哪個地方註冊的,但可以知道的是當用戶需要發送某個設置時(其實輪詢不會一直髮送控制,只會在需要初始化的時候發送一次控制信息),通過client一直往下調用最後執行adapter->algo->(master_xfer(adap,msg,num));利用回調函數調用最底層代碼實現真正的硬件發送。
總結:作爲I2C的client,如果框架圖這樣,它註冊在I2C的虛擬總線上(這部分屬於i2c-core實現的內容),工作在內核空間,它向上提供了open,close和poll接口,向下提供了adapter,繼而實現硬件傳輸,而驅動程序中的probe函數,很重要的一個功能就是實現了這樣一個client。