在Linux2.6 以後的設備驅動模型中,需要關心總線、設備和驅動3個實體,總線將設備和驅動綁定。在系統每註冊一個設備的時候,會尋找與之匹配的驅動;相反 ,在系統每註冊一個驅動的時候,會尋找與之匹配的設備,而匹配由總線完成。
一個現實的Linux 設備和驅動通常都需要掛接在一種總線上,對於本身依附於PCI、USB、I2C、SPI等的設備而言,這自然不是問題,但是在嵌入式系統裏面,在SoC系統中集成的獨立外設控制器、掛接在SOC內存空間的外設等不依附於此類總線。在linux中實現了一種虛擬總線,稱爲platform總線,相應的設備稱爲platform_device ,而驅動稱爲 platform_driver。 所謂的platform_device 並不是與字符設備、塊設備和網絡設備並列的概念,而是linux 系統提供的一種附加手段,例如,我們通常把在SoC內部集成的I2C、RTC、LCD、看門狗等控制器都歸納爲platform_device,而它們本身是字符設備。
platform_device結構體的定義如下
struct platform_device { // platform總線設備
const char * name; // 平臺設備的名字
int id; // ID 是用來區分如果設備名字相同的時候(通過在後面添加一個數字來代表不同的設備,因爲有時候有這種需求)
boo id_auto;
struct device dev; // 內置的device結構體
u32 num_resources; // 資源結構體數量
struct resource * resource; // 指向一個資源結構體數組
const struct platform_device_id *id_entry; // 用來進行與設備驅動匹配用的id_table表
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata; // 自留地 添加自己的東西
};
其中struct resource結構體的定義如下,描述了platform_device 的資源,本身由一個結構體表示
struct resource { // 資源結構體
resource_size_t start; // 資源的起始值,如果是地址,那麼是物理地址,不是虛擬地址
resource_size_t end; // 資源的結束值,如果是地址,那麼是物理地址,不是虛擬地址
const char *name; // 資源名
unsigned long flags; // 資源的標示,用來識別不同的資源
struct resource *parent, *sibling, *child; // 資源指針,可以構成鏈表
};
我們通常關心的是start,end,flags 三個參數,它們分別標明瞭資源的開始值、結束值和類型,flags可以爲IORESOURCE_IO、IORESOURCE_MEM、IORESOURCE_IRQ、IORE-SOURCE_DMA等。start、end的 含義會隨着flags而變更,如當flags爲IORESOURCE_MEM時,start、end分別表示該platform_device佔據的 內存的開始地址和結束地址;當flags爲IORESOURCE_IRQ時,start、end分別表示該platform_device使用的 中斷號的開始值和結束值,如果只使用了1箇中斷號,開始和結束值相同。對於同種類型的資源而言,可 以有多份,例如說某設備佔據了兩個內存區域,則可以定義兩個IORESOURCE_MEM資源。
對resource的定義也通常在BSP的板文件中進行,而在具體的設備驅動中通過platform_get_resource() 這樣的API來獲取,此API的原型爲
struct resource *platform_get_resource(struct platform_device *, unsigned int, unsigned int);
platform_driver 結構體的定義如下
struct platform_driver {
int (*probe)(struct platform_device *); // 這個probe函數其實和 device_driver中的是一樣的功能,但是一般是使用device_driver中的那個
int (*remove)(struct platform_device *); // 卸載平臺設備驅動的時候會調用這個函數,但是device_driver下面也有,具體調用的是誰這個就得分析了
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver; // 內置的device_driver 結構體
const struct platform_device_id *id_table; // 該設備驅動支持的設備的列表 他是通過這個指針去指向 platform_device_id 類型的數組
bool prevent_deferred_probe;
};
其中struct device_driver結構體的定義爲
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner;
const char *mod_name; /* used for built-in modules *
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups;
const struct dev_pm_ops *pm;
struct driver_private *p;
};
platform的接口函數
int platform_driver_register(struct platform_driver *); // 用來註冊我們的設備驅動
void platform_driver_unregister(struct platform_driver *); // 用來卸載我們的設備驅動
int platform_device_register(struct platform_device *); // 用來註冊我們的設備
void platform_device_unregister(struct platform_device *); // 用來卸載我們的設備
系統爲platform總線定義一個bus_type的實例platform_bus_type
struct bus_type platform_bus_type = {
.name = "platform"
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
這裏重點關注成員函數match(),其確定device和driver如何匹配。
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
可以看出,匹配platform_device和platform_driver有4種可能性,一是基於設備樹風格的匹配;二是基於ACPI風格的匹配;三是匹配ID表(即platform_device設備名是否出現在platform_driver的ID 表內);第四種是匹配platform_device設備名和驅動的名字。匹配platform_device和platform_driver主要看二者的name字段是否相同。(name必須要相同才能匹配)
對於linuxARM平臺而言,對設備platform_device的定義通常在BSP 的板級配置文件中實現,在板文件中,將platform_device歸納與一個數組,最終通過 platform_add_devices() 函數統一註冊。
platform_add_devices() 函數可以將平臺設備添加到系統中,這個函數的原型爲:
int platform_add_devices(struct platform_device **devs,int num);
該函數的第一個參數爲平臺設備數組的指針,第二個參數爲平臺設備的數量,它內部調用了platform_device_register() 函數以註冊單個的平臺設備。在linux3以後,ARMlinux 不太喜歡人們以編碼的形式去填寫platform_device和註冊,而傾向於根據設備數中的內容自動展開platform_device。
下面將之前寫的簡單字符設備驅動改爲platform總線的字符設備
先寫一個設備框架mymodule_device.c,只添加設備驅動匹配名稱
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <linux/device.h>
#include <linux/platform_device.h>
static struct platform_device mymodule_dev = {
.name = "mymodules",
.id = -1,
};
static int mymodule_dev_init(void)
{
int result = platform_device_register(&mymodele_dev);
return result;
}
static void mymodule_dev_exit(void)
{
platform_device_unregister(&mymodule_dev);
}
module_init(mymodule_dev_init);
module_exit(mymodule_dev_exit);
MODULE_AUTHOR("HaoRan");
MODULE_LICENSE("GPL");
在修改設備驅動文件mymodule_driver.c
//添加頭文件
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <linux/device.h>
#include <linux/poll.h>
#include <linux/platform_device.h>
static int major=0,minor=0;
#define BUF_CLEAR 0x1
//定義cdev結構體
struct mymodule_dev{
struct cdev cdev;
unsigned char buf[512];
struct mutex mutex;
unsigned int current_len;//buf中實際數據長度
wait_queue_head_t r_wait;//定義讀寫等待隊列頭部
wait_queue_head_t w_wait;
struct fasync_struct *async_queue; //異步結構體
};
struct mymodule_dev *mydev;
struct class *my_class;
struct device *my_device;
static int mymodule_fasync(int fd,struct file *filp,int mode)
{
struct mymodule_dev *dev = filp->private_data;
return fasync_helper(fd,filp,mode,&dev->async_queue);
}
static int mymodule_open(struct inode *inode,struct file *filp)
{
filp->private_data = mydev;
return 0;
}
static int mymodule_release(struct inode *inode,struct file *filp)
{
mymodule_fasync(-1,filp,0);
return 0;
}
static ssize_t mymodule_read(struct file *filp,char __user *buf,size_t count,loff_t *ppos)
{
int ret = 0;
struct mymodule_dev *dev = filp->private_data;
DECLARE_WAITQUEUE(wait,current);//定義等待隊列元素
mutex_lock(&dev->mutex);
add_wait_queue(&dev->r_wait,&wait);//等待隊列頭部添加到等待隊列中
while(dev->current_len ==0){
if(filp->f_flags & O_NONBLOCK){ //非阻塞時
ret = -EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE); //改變進程狀態
mutex_unlock(&dev->mutex);
schedule();
if(signal_pending(current)){ //如果是因爲信號喚醒
ret = -ERESTARTSYS;
goto out2;
}
mutex_lock(&dev->mutex);
}
if(count > dev->current_len)
count = dev->current_len;
//內核空間->用戶空間
if(copy_to_user(buf,dev->buf,count)){
ret = -EFAULT;
goto out;
}else{
memcpy(dev->buf,dev->buf+count,dev->current_len-count);
dev->current_len -= count;
printk(KERN_INFO"Read %d bytes,current_len %d\n",count,dev->current_len);
wake_up_interruptible(&dev->w_wait);//喚醒可能阻塞的寫進程
ret = count;
}
out:
mutex_unlock(&dev->mutex);
out2:
remove_wait_queue(&dev->w_wait,&wait); //將元素移除等待隊列
set_current_state(TASK_RUNNING); //設置進程狀態
return ret;
}
static ssize_t mymodule_write(struct file *filp,const char __user *buf,size_t count,loff_t *ppos)
{
int ret = 0;
struct mymodule_dev *dev = filp->private_data;
DECLARE_WAITQUEUE(wait,current);
mutex_lock(&dev->mutex);
add_wait_queue(&dev->w_wait,&wait);
while(dev->current_len == 512){
if(filp->f_flags & O_NONBLOCK){
ret = -EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE);
mutex_unlock(&dev->mutex);
schedule();
if(signal_pending(current)){
ret = -ERESTARTSYS;
goto out2;
}
mutex_lock(&dev->mutex);
}
if(count > 512 - dev->current_len)
count = 512 - dev->current_len;
//用戶空間->內核空間
if(copy_from_user(dev->buf+dev->current_len,buf,count)){
ret = -EFAULT;
goto out;
}else{
dev->current_len += count;
printk(KERN_INFO"Writen %d bytes,current_len: %d\n",count,dev->current_len);
wake_up_interruptible(&dev->r_wait);
if(dev->async_queue){
kill_fasync(&dev->async_queue,SIGIO,POLL_IN);
printk(KERN_DEBUG"%s kill SIGIO\n",__func__);
}
ret = count;
}
out:
mutex_unlock(&dev->mutex);
out2:
remove_wait_queue(&dev->w_wait,&wait);
set_current_state(TASK_RUNNING);
return ret;
}
static unsigned int mymodule_poll(struct file *filp,struct poll_table_struct *wait)
{
unsigned int mask = 0;
struct mymodule_dev *dev = filp->private_data;
mutex_lock(&dev->mutex);
poll_wait(filp,&dev->r_wait,wait);
poll_wait(filp,&dev->w_wait,wait);
if(dev->current_len !=0)
mask |= POLLIN | POLLRDNORM;
if(dev->current_len != 512)
mask |= POLLOUT | POLLWRNORM;
mutex_unlock(&dev->mutex);
return mask;
}
static long mymodule_ioctl(struct file *filp,unsigned int cmd,unsigned long arg)
{
struct mymodule_dev *dev = filp->private_data;
switch(cmd)
{
case BUF_CLEAR:
mutex_lock(&dev->mutex);
memset(dev->buf,0,512);
mutex_unlock(&dev->mutex);
printk(KERN_INFO"globalmem is set to zero\n");
break;
default:
return - EINVAL;
}
return 0;
}
//file_operation設備驅動文件操作結構體
static struct file_operations mymodule_fops = {
.owner = THIS_MODULE,
.open = mymodule_open,
.release = mymodule_release,
.read = mymodule_read,
.write = mymodule_write,
.poll = mymodule_poll,
.compat_ioctl = mymodule_ioctl,
.fasync = mymodule_fasync,
};
//初始化並添加cdev結構體
static void mymodule_cdev_setup(struct mymodule_dev *dev)
{
int err,devno=MKDEV(major,minor);
//初始化
cdev_init(&dev->cdev,&mymodule_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &mymodule_fops;
//註冊,添加
err = cdev_add(&dev->cdev,devno,1);
if(err)
printk(KERN_NOTICE"error %d adding mymodule",err);
}
static int mymodule_probe(void)
{
//申請設備號
int result;
dev_t devno = MKDEV(major,minor);
if(major)
result = register_chrdev_region(devno,1,"mymodule");
else{
result = alloc_chrdev_region(&devno,minor,1,"mymodule");
major = MAJOR(devno);
minor = MINOR(devno);
}
if(result<0)
return result;
//動態申請設備結構體內存
mydev = kmalloc(sizeof(struct mymodule_dev),GFP_KERNEL);
if(!mydev){
result=-ENOMEM;
goto fail_malloc;
}
memset(mydev,0,sizeof(struct mymodule_dev));
//初始化互斥體
mutex_init(&mydev->mutex);
//cdev字符設備的初始化和添加
mymodule_cdev_setup(mydev);
//初始化等待隊列長度
init_waitqueue_head(&mydev->r_wait);
init_waitqueue_head(&mydev->w_wait);
//註冊設備節點
my_class = class_create(THIS_MODULE,"mymodule_t");
my_device = device_create(my_class,NULL,MKDEV(major,minor),NULL,"mymodules");
return 0;
fail_malloc:unregister_chrdev_region(devno,1);
return result;
}
static int mymodule_remove(void)
{
device_destroy(my_class,MKDEV(major,minor));
class_destroy(my_class);
//刪除cdev結構體
cdev_del(&mydev->cdev);
kfree(mydev);
//註銷設備號
unregister_chrdev_region(MKDEV(major,minor),1);
}
static struct platform_driver mymodule_drv = {
.porbe = mymodule_probe,
.remove = mymodule_remove,
.driver = {
.owner = THIS_MODULE,
.name = "mymodules",
},
};
//模塊加載
int __init mymodule_init(void)
{
int result = platform_driver_register(&mymodule_drv);
return result;
}
//模塊卸載
void __exit mymodule_exit(void)
{
platform_driver_unregister(&mymodule_drv);
}
module_init(mymodule_init);
module_exit(mymodule_exit);
MODULE_AUTHOR("HaoRan");
MODULE_LICENSE("GPL");
編譯後兩個.ko 都加載後,在/dev下才出現 mymodules的設備文件,並且在/sys/devices/platform 下會出現mymodules的文件夾。在mymodules文件夾下會有driver文件,它是指向/sys/bus/platform/devices/mymodules 的符號鏈接,這證明驅動和設備匹配上了。
上述的mymodule_device.c 的文件可以不寫,只需要在內核/arch/arm/mach-vexpress/ct-ca9x4.c 中添加如下代碼代碼,然後重新編譯內核與驅動。
static struct platform_device mymodule_device = {
.name = "mymodules",
.id = -1,
}