1 开发环境
Linux Kernel 4.18.0 QEMU 5.2.0-vexpress Source Insight 3.5
2 字符设备驱动
Linux从各异的设备中提取共性,将其划分成三大类:字符设备、块设备和网络设备。 常见的字符设备有键盘、鼠标、液晶显示、打印机等。
2.1 字符设备结构体
struct cdev {
struct kobject kobj;
struct module * owner;
const struct file_operations * ops;
struct list_head list;
dev_t dev;
unsigned int count;
} __randomize_layout;
file_operations 操作集合,定义了字符设备驱动提供给虚拟文件系统的接口函数
struct file_operations {
. . .
ssize_t ( * read) ( struct file * , char __user * , size_t, loff_t * ) ;
ssize_t ( * write) ( struct file * , const char __user * , size_t, loff_t * ) ;
. . .
} __randomize_layout;
#define MAJOR(dev) ((dev)>>8)
#define MINOR(dev) ((dev) & 0xff)
#define MKDEV(ma,mi) ((ma)<<8 | (mi))
2.2 字符设备操作集合
struct cdev * cdev_alloc ( void ) ;
int register_chrdev_region ( dev_t from, unsigned count, const char * name) ;
int alloc_chrdev_region ( dev_t * dev, unsigned baseminor, unsigned count, const char * name) ;
void cdev_init ( struct cdev * cdev, const struct file_operations * fops) ;
int cdev_add ( struct cdev * p, dev_t dev, unsigned count) ;
void unregister_chrdev_region ( dev_t from, unsigned count) ;
void cdev_del ( struct cdev * p) ;
3 设备驱动
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#define ERIC_SIZE (0x1000)
#define ERIC_MAJOR (230)
#define DEVICE_NUM (10)
#define ERIC_MAGIC ('g')
#define MEM_CLEAR _IO(ERIC_MAGIC, 0)
static int eric_major = ERIC_MAJOR;
module_param ( eric_major, int , S_IRUGO) ;
struct eric_dev {
struct cdev cdev;
unsigned char mem[ ERIC_SIZE] ;
} ;
struct eric_dev* eric_devp;
long eric_ioctl ( struct file * filp, unsigned int cmd, unsigned long arg)
{
struct eric_dev* dev = filp-> private_data;
switch ( cmd) {
case MEM_CLEAR:
memset ( dev-> mem, 0 , ERIC_SIZE) ;
printk ( KERN_INFO "mem is set to zero\n" ) ;
break ;
default :
return - EINVAL;
}
return 0 ;
}
loff_t eric_llseek ( struct file * filp, loff_t offset, int orig)
{
loff_t ret = 0 ;
switch ( orig) {
case SEEK_SET :
if ( ( offset< 0 ) || ( ( offset > ERIC_SIZE) ) )
ret = - EINVAL;
filp-> f_pos = ( unsigned int ) offset;
ret = filp-> f_pos;
break ;
case SEEK_CUR :
if ( ( ( filp-> f_pos+ offset) < 0 ) || ( ( filp-> f_pos + offset) > ERIC_SIZE) ) {
ret = - EINVAL;
}
filp-> f_pos + = ( unsigned int ) offset;
ret = filp-> f_pos;
break ;
case SEEK_END :
filp-> f_pos = ( unsigned int ) ( ERIC_SIZE+ offset) ;
ret = filp-> f_pos;
if ( ret > ERIC_SIZE)
ret = ERIC_SIZE;
break ;
default :
ret = - EINVAL;
break ;
}
printk ( KERN_INFO "eric_llseek\n" ) ;
return ret;
}
ssize_t eric_read ( struct file * filp, char __user * buf, size_t size, loff_t * ppos)
{
unsigned long p = * ppos;
unsigned int count = size;
int ret = 0 ;
struct eric_dev* dev = filp-> private_data;
if ( p >= ERIC_SIZE)
return 0 ;
if ( count > ERIC_SIZE - p)
count = ERIC_SIZE - p;
if ( copy_to_user ( buf, dev-> mem + p, count) )
ret = - EFAULT;
else {
* ppos + = count;
ret = count;
printk ( KERN_INFO "eric_read\n" ) ;
}
return ret;
}
ssize_t eric_write ( struct file * filp, const char __user * buf, size_t size, loff_t * ppos)
{
unsigned long p = * ppos;
unsigned int count = size;
int ret = 0 ;
struct eric_dev* dev = filp-> private_data;
if ( p >= ERIC_SIZE)
return 0 ;
if ( count > ERIC_SIZE - p)
count = ERIC_SIZE - p;
if ( copy_from_user ( dev-> mem + p, buf, count) )
ret = - EFAULT;
else {
* ppos + = count;
ret = count;
printk ( KERN_INFO "eric_write\n" ) ;
}
return ret;
}
int eric_open ( struct inode * inode, struct file * filp)
{
struct eric_dev* dev = container_of ( inode-> i_cdev, struct eric_dev, cdev) ;
filp-> private_data = dev;
printk ( KERN_INFO "eric_open\n" ) ;
return 0 ;
}
int eric_release ( struct inode * inode, struct file * filp)
{
printk ( KERN_INFO "eric_release\n" ) ;
return 0 ;
}
const struct file_operations eric_file_opers = {
. owner = THIS_MODULE,
. llseek = eric_llseek,
. unlocked_ioctl = eric_ioctl,
. read = eric_read,
. write = eric_write,
. open = eric_open,
. release = eric_release
} ;
static void eric_setup_cdev ( struct eric_dev* dev, int index)
{
int err = 0 ;
int devnum = MKDEV ( ERIC_MAJOR, index) ;
cdev_init ( & ( dev-> cdev) , & eric_file_opers) ;
dev-> cdev. owner = THIS_MODULE;
err = cdev_add ( & ( dev-> cdev) , devnum, 1 ) ;
if ( err)
printk ( KERN_INFO "error %d adding eric_dev%d" , err, index) ;
}
static int __init eric_char_init ( void )
{
dev_t devnum = MKDEV ( eric_major, 0 ) ;
int ret= 0 , i = 0 ;
printk ( "eric_char_init start\n" ) ;
if ( eric_major)
ret = register_chrdev_region ( devnum, DEVICE_NUM, "eric_char" ) ;
else {
ret = alloc_chrdev_region ( & devnum, 0 , DEVICE_NUM, "eric_char" ) ;
eric_major = MAJOR ( devnum) ;
}
if ( ret < 0 )
return ret;
eric_devp = kzalloc ( sizeof ( struct eric_dev) * DEVICE_NUM, GFP_KERNEL) ;
if ( eric_devp == NULL ) {
ret = - ENOMEM;
goto fail_malloc;
}
for ( i = 0 ; i < DEVICE_NUM; i++ )
eric_setup_cdev ( eric_devp+ i, i) ;
printk ( KERN_INFO "eric_char_init end\n" ) ;
return ret;
fail_malloc:
printk ( KERN_INFO "eric_char_init_fail_malloc\n" ) ;
unregister_chrdev_region ( devnum, DEVICE_NUM) ;
return ret;
}
module_init ( eric_char_init) ;
static void __exit eric_char_exit ( void )
{
dev_t devnum = MKDEV ( eric_major, 0 ) ;
int i;
for ( i = 0 ; i < DEVICE_NUM; i++ )
cdev_del ( & ( eric_devp + i) -> cdev) ;
if ( eric_devp)
kfree ( eric_devp) ;
if ( eric_major)
unregister_chrdev_region ( devnum, DEVICE_NUM) ;
printk ( KERN_INFO "eric_char_exit\n" ) ;
}
module_exit ( eric_char_exit) ;
MODULE_AUTHOR ( "eric dong" ) ;
MODULE_LICENSE ( "GPL v2" ) ;
4 QEMU 对应结果
5 参考书籍
Linux 设备驱动开发详解-基于最新的 Linux 4.0内核(宋宝华编著)