Android Linux 裝置驅動
今天記錄下如何寫一個 Android 下的裝置字元驅動(也算是工作總結),下面假設有一個 test 裝置 內容如下:
一、驅動模組初始化
//驅動載入
static int __init test_init(void){
//本函式中就可以做一些初始化操作,如申請 工作佇列等;若掛載在 平臺裝置上面,則新增程式碼如下
if (platform_driver_register(&test_driver)) {
printk(KERN_ERR "add test driver error\n");
return -1;
}
return 0;
}
static void __exit test_exit(void){
platform_driver_unregister(&test_driver);
}
//模組的載入和解除安裝 會呼叫 test_init 和 test_exit 函式
module_init(test_init);
module_exit(test_exit);
//下面這個LICENSE是必須要寫的
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("test device driver");
MODULE_AUTHOR("loumihua");
test_init 函式就是驅動模組的初始化工作,
1.如果驅動(就是這個驅動沒有對應的具體裝置等)不需要掛載到 平臺裝置 或者 i2c 裝置,這裡就可以進行初始化操作,如申請工作佇列 、建立裝置節點;
2.如果本裝置(這個驅動要操作的裝置)是掛載在 i2c 裝置上面,則通過函式 i2c_add_driver(&test_driver) 進行匹配,成功後在 對應的 probe 函式中進行初始化
3.如果本裝置(這個驅動要操作的裝置)是掛載在 平臺裝置上面,則通過函式 platform_driver_register 註冊裝置,成功後再 probe 函式中進行初始化
//匹配的 compatible,注意這個名稱一定要和dts 中寫的一致
#ifdef CONFIG_OF
static const struct of_device_id test_of_match[] = {
{.compatible = "testsensor"},
{}
};
#endif
//電源管理 對裝置上電和下電,無裝置的驅動是不需要的
#ifdef CONFIG_PM
static const struct dev_pm_ops test_pm_ops = {
.suspend = test_suspend,
.resume = test_resume,
};
#endif
static struct platform_driver test_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "test_sensor",
#ifdef CONFIG_PM
.pm = &test_pm_ops,
#endif
//dts 中通過 compatible 屬性進行匹配
#ifdef CONFIG_OF
.of_match_table = test_of_match,
#endif
},
.probe = test_probe, //當 通過compatible或id(沒有實現)匹配成功後呼叫該函式
.remove = test_remove,
};
如果是裝置驅動 宣告一個 platform_driver 的型別 test_driver, 若是掛載在 i2c 上面的裝置,宣告 i2c_driver 結構體資料struct i2c_driver test_driver = { … }; 其中 掛載在 i2c 上的裝置值 一個裝置需要通過 i2c匯流排傳輸資料,如Android 手機的加速度、地磁等一般都是掛載在 i2c 總線上,通過 i2c 匯流排與cpu進行通訊(資料傳輸);
當 註冊 成功後,會呼叫 probe 函式(如果驅動進入 init 函式後,沒有進入到該函式,主要去看 dts 配置是否正確,還有上面的compatible 對應的名稱等 )
static int test_probe(struct platform_device *pdev)
{
int err;
struct workqueue_struct *mworkque = NULL;
//一般會定義一個 test_device 結構體,該結構體物件 test_dev 中存放常用的變數
test_dev = kzalloc(sizeof(struct test_device), GFP_KERNEL);
if (test_dev == NULL) {
printk(KERN_ERR "allocate memory for test device fail\n");
return -ENOMEM;
}
// 下面部分其實就是對 結構體物件test_dev 中變數進行初始化
test_dev->delay_ns = 0;
test_dev->latency_ns = 0;
test_dev->first_enable = true;
//如果是 具體的裝置,一般裝置是需要設定初始化的,如加速度裝置需要配置暫存器,設定量程等
err = test_init_device();
if(err){
printk(KERN_ERR "test init device fail\n");
goto sen_event_fail;
}
atomic_set(&test_dev->enable, 1);
mutex_init(&test_dev->mutex_lock);
/*設定工作佇列,註冊中斷(若裝置是中斷方式),或者定時器實現輪詢方式*/
mworkque = create_workqueue("test_workqueue");
test_dev->workque = mworkque;
if(!test_dev->workque){
printk(KERN_ERR "test create work queue err\n");
goto err_workque;
}
INIT_WORK(&test_dev->work, test_work_handler);
//初始化定時器
hrtimer_init(&test_dev->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
test_dev->timer.function = test_timer_func;
test_dev->debounce_time = ktime_set(0, 50000000);
//註冊input device
test_dev->input_dev = input_allocate_device();
err = input_register_device(test_dev->input_dev);
if (err < 0) {
printk("could not register input device\n");
}
//register misc device
err = misc_register(&test_misc_device);
if(err){
printk(KERN_ERR "%s: can not register sensor misc device\n", __func__);
goto err_misc_register;
}
//在sys目錄下建立節點
err = sysfs_create_group(&test_misc_device.this_device->kobj, &test_attribute_group);
if (err) {
printk(KERN_ERR "sysfs create fail\n");
goto err_sysgroup;
}
return 0;
err_sysgroup:
sysfs_remove_group(&test_misc_device.this_device->kobj, &test_attribute_group);
err_misc_register:
misc_deregister(&test_misc_device);
err_workque:
destroy_workqueue(test_dev->workque);
return -1;
}
對於Android 下的 linux 裝置,probe 中 一般為下面操作
1.裝置進行暫存器設定等初始化;對宣告的裝置對應的結構體分配記憶體並進行初始化
2.建立工作佇列,然後根據裝置使用中斷方式或輪詢方式,中斷則註冊中斷;輪詢建立定時器;在工作任務中上報資料
3.建立misc 裝置及屬性節點
4.一般會註冊input 裝置,主要是驅動中裝置向上層上報資料時,其實是將資料存到input一個快取中
input 是一個event 驅動,上層會通過節點input節點讀取 裝置上報的資料,在傳輸到 Android java層
下面是 宣告 miscdevice 裝置並定義對應的 檔案操作
static struct file_operations test_fops = {
.owner = THIS_MODULE,
.open = test_open,
.read = test_read,
.release = test_release,
.unlocked_ioctl = test_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = test_ioctl,
#endif
};
static struct miscdevice test_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = TEST_MISC_DEV_NAME,
.fops = &test_fops,
};
下面是對應的屬性節點
static DEVICE_ATTR(testactive, S_IWUSR | S_IRUGO, test_show_active, test_store_active);
static struct attribute *test_attribute[] = {
&dev_attr_testactive.attr,
NULL,
};
static struct attribute_group test_attribute_group = {
.attrs = test_attribute
};
上層(一般是hardware層)對裝置進行操作可以通過屬性節點,或者通過 ioctl 函式對裝置進行通訊(即讀寫裝置,如加速度);當上層(hardware、jni、java)去通過節點或ioctl去操作裝置是,有些敏感的操作會涉及到 selinux 許可權問題,需要在許可權檔案中新增許可權
下面是函式的實現
//work佇列回撥函式,主要是上報資料,如果是輪詢就再次開啟定時器,中斷就enable中斷,這裡是輪詢
static void test_work_handler(struct work_struct *work){
int err = 0;
int pdata[2] = {0};
u32 data;
u32 val;
int en;
struct timespec time;
struct temp_data tpdata;
/*report data*/
int64_t cur_ns ,m_pre_ns = 0;
int64_t delay_ms = 100;
time.tv_sec = time.tv_nsec = 0;
get_monotonic_boottime(&time);
cur_ns = time.tv_sec*1000000000LL+time.tv_nsec;
m_pre_ns = test_dev->time;
test_dev->time = cur_ns;
test_read_data(addr_w, cmd_t0, addr_r,pdata);
test_dev->t0 = val;
tpdata.x = val;
tpdata.status = 0;
tpdata.reserved = 0;
tpdata.handle = test_dev->handle;
//上報資料
while((cur_ns - m_pre_ns) >= delay_ms*1800000LL) {
m_pre_ns += delay_ms*1000000LL;
tpdata.timestamp = m_pre_ns;
test_report_data(&tpdata);
}
//啟動定時器
if(en){
hrtimer_start(&test_dev->timer, test_dev->debounce_time, HRTIMER_MODE_REL);
}
return;
}
//定時器時間到,就啟動一個工作 將其加入到工作佇列,然後就會調到上面這個函式
static enum hrtimer_restart test_timer_func(struct hrtimer *data)
{
queue_work(test_dev->workque, &test_dev->work);
return HRTIMER_NORESTART;
}
//下面是屬性節點的實現,這個是從屬性節點中獲取值,一般在終端中 cat 這個節點
static ssize_t test_show_active(struct device *dev, struct device_attribute *attr, char *buf)
{
int err = 0;
int enable = 0;
enable = atomic_read(&test_dev->enable);
//通過 sprintf 函式將enable 的值傳到使用者空間 ,在終端中可以看到
err = sprintf(buf,"%d\n",enable);
if(err){
printk(KERN_ERR "test active fail\n");
}
return err;
}
//下面是屬性節點的實現,這個是向屬性節點中寫值,一般在終端中 eaho 這個節點
static ssize_t test_store_active(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
int ret = 0;
unsigned int enable;
//獲取到使用者空間寫入的值,這個值是在buf中,通過格式化轉化到 enable 中
ret = sscanf(buf, "%d", &enable);
//這樣寫主要是當Android上層enable(開啟)裝置(如sensor)時,開啟定時器進行上報資料
if(enable == 1){
atomic_set(&test_dev->enable,1);
hrtimer_start(&test_dev->timer, test_dev->debounce_time, HRTIMER_MODE_REL);
}else if(enable == 0)
{//上層關閉裝置,不在上報資料
atomic_set(&test_dev->enable,0);
}
//下面這個返回值一定要這樣寫,否則會報錯
return count;
}
//上報資料
static int test_report_data(struct test_data *data){
int err = 0;
mutex_lock(&test_dev->mutex_lock);
.......
input_report_abs(data->input_dev, ABS_DISTANCE, event);
input_sync(data->input_dev);
mutex_unlock(&test_dev->mutex_lock);
return err;
}
//當上層呼叫 ioctl 函式時,會呼叫到該函式中,主要也是獲取資料或者向裝置寫值操作裝置
static long test_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int err = 0;
void __user *data;
switch(cmd){
//如通過ioctl函式進行enable裝置
case TESTDEV_IOCTL_ENABLE:{
int ret = 0;
int buf = 0;
data = (void __user *)arg; //使用者空間資料在arg
//從使用者空間獲取資料,呼叫下面這個函式,資料傳到 buf 中
ret = copy_from_user(&buf, data, sizeof(buf));
if (ret)
{
err = -EINVAL;
break;
}
ret = test_enable(buf);
if(ret){
printk(KERN_ERR "test calibrate err\n");
err = -EINVAL;
}
}
break;
//ioctl 函式讀取資料
case TESTDEV_IOCTL_READ_RAWDATA:{
int ret = 0;
u32 val;
data = (void __user *)arg; //使用者空間資料
//獲取資料
ret = test_get_data(&val);
if(ret < 0){
printk(KERN_ERR "get test data fail\n");
err = -EINVAL;
break;
}
//將獲取的資料通過下面函式copy到使用者空間
ret = copy_to_user(data, &val, sizeof(val));
if (ret < 0)
{
err = -EINVAL;
break;
}
}
break;
}
return err;
}
//檔案操作函式
static int test_open(struct inode* inode, struct file* filp)
{
nonseekable_open(inode, filp);
return 0;
}
static int test_release(struct inode* inode, struct file* filp){
return 0;
}
static ssize_t test_read(struct file *file, char __user *buffer,
size_t count, loff_t *ppos)
{
ssize_t read_cnt = 0;
...
return read_cnt;
}
//下面主要是裝置的電源操作,如當手機滅屏時,對加速度等進行下電操作(僅僅是舉例)
#ifdef CONFIG_PM
static int test_suspend(struct device *dev){
int ret = 0;
//complete device power down
return ret;
}
static int test_resume(struct device *dev){
int ret = 0;
//complete device power up
return ret;
}
#endif
上面就是一個 Android 下 簡單裝置的驅動,不過僅僅是框架思路,如果要實現一個裝置驅動,可以在此思路基礎上,比照一個完整的驅動進行修改即可