1. 平台设备驱动
在linux2.6以后的设备驱动模型中,只关心设备、驱动和总线这三个实体,总线将设备驱动绑定。在向系统注册一个设备时会由总线匹配对应的驱动,相反当向系统注册一个驱动时由总线匹配出对应的设备。
在linux设备和驱动通常要挂接在某条总线上,对于IIC、SPI、USB等设备有自己的物理总线,但是在嵌入式的系统中很多的设备并不能找到自己的物理总线。基于这一背景linux发明了一种虚拟总线,称为platform总线,与之对应的设备称之为platform_device,驱动称之为platform_driver。platform_device设备是字符设备。
1.1 platform_device结构体
struct platform_device {
const char * name; //设备名
int id;
struct device dev; //设备结构体
u32 num_resources; //设备资源个数
struct resource * resource; //设备资源
struct platform_device_id *id_entry;
/* arch specific additions */
struct pdev_archdata archdata;
};
1.1.1 设备资源结构体
struct resource {
resource_size_t start; //资源开始位置(地址||中断号之类)
resource_size_t end; //资源结束的位置
const char *name; //资源的名称
unsigned long flags; //何种资源
struct resource *parent, *sibling, *child;
};
flages标志可以为: IORESOURCE_TYPE_BITS、IORESOURCE_IO、IORESOURCE_MEM、IORESOURCE_IRQ、IORESOURCE_DMA,当为IORESOURCE_MEM时start为开始地址,end为结束地址。当为IORESOURCE_IRQ时start为中断号开始值,end为中中断号结束值。可以使用struct resource *platform_get_resource(struct platform_device *,unsigned int ,unsigned int);来获得资源。
1.1.2 设备结构体
struct device {
struct device *parent;
struct device_private *p;
struct kobject kobj;
const char *init_name; /* initial name of the device */
struct device_type *type;
struct semaphore sem; /* semaphore to synchronize calls to
* its driver.
*/
struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this
device */
void *platform_data; /* Platform specific data, device
core doesn't touch it */
struct dev_pm_info power;
#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
u64 *dma_mask; /* dma mask (if dma'able device) */
u64 coherent_dma_mask;/* Like dma_mask, but for
alloc_coherent mappings as
not all hardware supports
64 bit addresses for consistent
allocations such descriptors. */
struct device_dma_parameters *dma_parms;
struct list_head dma_pools; /* dma pools (if dma'ble) */
struct dma_coherent_mem *dma_mem; /* internal for coherent mem
override */
/* arch specific additions */
struct dev_archdata archdata;
dev_t devt; /* dev_t, creates the sysfs "dev" */
spinlock_t devres_lock;
struct list_head devres_head;
struct klist_node knode_class;
struct class *class;
const struct attribute_group **groups; /* optional groups */
void (*release)(struct device *dev);
};
platform_data,携带设备的相关描述信息。使用dev_get_platdata()获得相关的描述信息。
1.1.3 向内核注册/注销设备
int platform_device_register(struct platform_device *); //向内核注册设备void platform_device_unregister(struct platform_device *);//注销设备
1.2 platform_driver结构体
struct platform_driver {
int (*probe)(struct platform_device *); //设备驱动匹配时调用
int (*remove)(struct platform_device *); //设备注销时调用
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
struct platform_device_id *id_table;
};
1.2.1 struct device_drivice
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 */
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;
};
1.2.2 驱动的注册与注销
int platform_driver_register(struct platform_driver *); //向内核注册驱动
void platform_driver_unregister(struct platform_driver *); //从内核注销设备
1.3 bus_type的实例platform_bus_type(内核源码/driver/base/platform.c文件)
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
1.3.1 匹配函数match
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);
/* When driver_override is set, only bind to the matching driver */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* 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);
}
由此可见设备和驱动的匹配有四种:①通过设备树②通过ACPI③通过ID④通过name
2. 混杂设备
linux驱动程序的设计都倾向分层的思想,所以各个具体的设备都能找到自己的归属类型,从而套用他的架构里去,并且只需要实现其底层的那一部分。但是有些设备确实无法找的它的类型,此类设备一般使用miscdevice设备。
2.1 miscdevice结构体
struct miscdevice {
int minor; //次设备号
const char *name; //设备名称
const struct file_operations *fops; //操作
struct list_head list;
struct device *parent;
struct device *this_device;
const char *nodename;
mode_t mode; //所属模块
};
miscdevice设备的主设备号是固定的,MIC_MAJOR为10,MISC_DYNAMIC_MINOR内核分配次设备号。
2.2 注册和注销miscdevice设备
int misc_register(struct miscdevice * misc); //注册
int misc_deregister(struct miscdevice *misc); //注销
3. 实例
3.1 设备
/*
* dev_key.c
*
* Created on: 2017年5月29日
* Author: chy
*/
#include <linux/miscdevice.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#define GPGCON 0x56000060
#define EINTPEND 0x560000a8
struct resource key_resource[] = {
[0] = { //mem
.start = GPGCON,
.end = GPGCON + 8,
.name = "gpgcon",
.flags = IORESOURCE_MEM,
},
[1] = { //irq
.start = IRQ_EINT8,
.end = IRQ_EINT13,
.name = "irq",
.flags = IORESOURCE_IRQ,
},
};
struct platform_device dev;
int init_dev()
{
printk(KERN_WARNING "dev start!\n");
dev.num_resources = ARRAY_SIZE(key_resource);
dev.resource = key_resource;
dev.name = "keys";
int ans = platform_device_register(&dev); // platform_device_add
if(ans){
printk(KERN_WARNING "ans dev faile!\n");
platform_device_put(&dev);
return ans;
}
return 0;
}
int __exit exit_dev()
{
platform_device_unregister(&dev);
}
MODULE_LICENSE("GPL");
module_init(init_dev);
module_exit(exit_dev);
3.2 驱动
/*
* mini2440_keys.c
*
* Created on: 2017年5月29日
* Author: chy
*/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/wait.h>
#include <asm/uaccess.h>
#include <linux/sched.h>
#include <linux/param.h>
#include <linux/string.h>
struct keys_dri{
wait_queue_head_t wait_key; //等待队列
struct work_struct* work_key; //工作队列
struct timer_list timer_key; //定时器
struct miscdevice mis_dev; //混杂设备
struct resource *re_dev;
unsigned long *gpgcon;
unsigned long *gpgdat;
unsigned long even_date;
char buffer[30];
};
static struct keys_dri key_dri;
void work_func(struct work_struct *work) //处理底半部中断函数
{
mod_timer(&key_dri.timer_key,jiffies + HZ / 10); //时钟中断由系统定时硬件以周期性的间隔产生,这个间隔由内核根据 HZ 值来设定,
//HZ 是一个体系依赖的值,在 <Linux/param.h>中定义或该文件包含的某个子平
//台相关文件中。作为通用的规则,即便如果知道 HZ 的值,在编程时应当不依赖这个
//特定值,而始终使用HZ。对于当前版本,我们应完全信任内核开发者,他们已经选择
//了最适合的HZ值,最好保持 HZ 的默认值。
return;
}
void time_func(unsigned long data)//定时器中断函数
{
unsigned long date;
date = readw(key_dri.gpgdat) & 0xffff; //读取gpgdat寄存器的值
//判断那个按键被按下
if(!(date & (0x1)))
key_dri.even_date = 1;
else if(!(date & (0x1 << 3)))
key_dri.even_date = 2;
else if(!(date & (0x1 << 5)))
key_dri.even_date = 3;
else key_dri.even_date = date;
sprintf(key_dri.buffer,"你按下了%d号按键",key_dri.even_date);
wake_up(&key_dri.wait_key);
return;
}
irqreturn_t interrupt_func(int riq,void *dev) //处理顶半部中断
{
schedule_work(key_dri.work_key); //调度工作队列
return IRQ_HANDLED;
}
int open_func(struct noid* noid_key,struct file* file_key)//打开设备
{
unsigned long data;
data = readw(key_dri.gpgcon); //设置端口为中断工作模式
data |= 0x222;
writew(data,key_dri.gpgcon);
return 0;
}
ssize_t read_func(struct file* file_key,char __user* buf_key,size_t len,loff_t *off_key) //读取按键状态
{
wait_event(key_dri.wait_key,key_dri.even_date); //等待even_date事件发生
unsigned long size;
size = copy_to_user(buf_key,key_dri.buffer,sizeof(key_dri.buffer) + 1); //由内核态复制到用户态
key_dri.even_date = 0; //事件清空
return sizeof(key_dri.buffer) - size;
}
int close_key() //关闭设备文件
{
return 0;
}
struct file_operations file_oper = {
.read = read_func,
.open = open_func,
.release = close_key,
};
int probe_keys(struct platfrom_device *dev)
{
key_dri.mis_dev.minor = MISC_DYNAMIC_MINOR; //混杂设备次设备号
key_dri.mis_dev.name = "key"; //混杂设备名
key_dri.mis_dev.fops = &file_oper; //操作结构体
key_dri.even_date = 0;//事件
strcpy(key_dri.buffer,"你按下的是");
key_dri.work_key = kmalloc(sizeof(struct work_struct),GFP_KERNEL); //给工作队列申请内存
INIT_WORK(key_dri.work_key,work_func); //初始化工作队列
init_timer(&key_dri.timer_key); //初始化定时器
key_dri.timer_key.function = time_func; //定时器中断处理函数
add_timer(&key_dri.timer_key); //向系统注册定时器
key_dri.re_dev = platform_get_resource(dev,IORESOURCE_IRQ,0); //获取设备资源
request_irq(key_dri.re_dev->start,interrupt_func,IRQF_TRIGGER_FALLING,"key1",(void*)1); //GPG0
request_irq(key_dri.re_dev->start +3 ,interrupt_func,IRQF_TRIGGER_FALLING,"key2",(void*)2); //GPG3
request_irq(key_dri.re_dev->end,interrupt_func,IRQF_TRIGGER_FALLING,"key3",(void*)3); //GPG5
key_dri.re_dev = platform_get_resource(dev,IORESOURCE_MEM,0); //获取设备资源
key_dri.gpgcon = ioremap(key_dri.re_dev->start,key_dri.re_dev->end - key_dri.re_dev->start + 1);
key_dri.gpgdat = key_dri.gpgcon + 1;
init_waitqueue_head(&key_dri.wait_key); //初始化等待队列
if(misc_register(&key_dri.mis_dev)) //注册混杂设备
printk(KERN_WARNING " 注册失败!");
return 0;
}
int remove_keys(struct platform_device *dev)
{
free_irq(key_dri.re_dev->start,(void*)1);
free_irq(key_dri.re_dev->start +3,(void*)2);
free_irq(key_dri.re_dev->end,(void*)3);
iounmap(key_dri.gpgcon);
misc_deregister(&key_dri.mis_dev);
return 0;
}
static struct platform_driver platform_keys = {
.driver = {
.name = "keys",
.owner = THIS_MODULE,
},
.probe = probe_keys,
.remove = remove_keys
};
int init_keys(void)
{
return platform_driver_register(&platform_keys);
}
void exit_keys(void)
{
platform_driver_unregister(&platform_keys);
}
MODULE_LICENSE("GPL");
module_init(init_keys);
module_exit(exit_keys);
3.3 应用
/*
* key_app.c
*
* Created on: 2017年5月30日
* Author: chy
*/
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFSIZE 40
char buffer[BUFSIZE];
int main(int argc,char *argv[])
{
int fd;
fd = open("/dev/key", O_RDONLY ); //打开设备文件
if(fd < 0){
fprintf(stderr,"/dev/key文件打开失败\n");
return 0;
}
printf("/dev/key打开成功\n");
if(read(fd,buffer,BUFSIZE) < 0 ){ //读取按键的值
fprintf(stderr,"读取失败\n");
return 0;
}
printf("%s\n",buffer);
close(fd); //关闭文件
return 0;
}
3.4 Makefile
./src/device/Makefile
obj-m += dev_key.o
clean:
rm -fr *.ko *.o *mod* Mod*
./src/driver/Makefile
obj-m += mini2440_keys.o
clean:
rm -fr *.ko *.o *mod* Mod*
kernel = /home/chy/work/linux-2.6.32.2
device = $(PWD)src/device
driver = $(PWD)src/driver
app = $(PWD)src/app
all:
make -C $(kernel) M=$(device) modules
make -C $(kernel) M=$(driver) modules
make -C $(app) all
clean:
make -C $(device) clean
make -C $(driver) clean
make -C $(app) clean