一、 字符设备驱动程序框架
编写驱动程序的套路:
- 确定主设备号,也可以让内核分配
- 定义自己的file_operations结构体
- 实现对应的drv_open/drv_read/drv_write等函数,填入file_operations结构体
- 把file_operations结构体告诉内核:register_chrdev
- 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数
- 有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用unregister_chrdev
- 其他完善:提供设备信息,自动创建设备节点:class_create, device_create
二、新的注册方法
- 注册字符设备区域
- 有主设备号:register_chrdev_region
- 无主设备号:alloc_chrdev_region
- 分配/设置/注册cdev
- cdev_alloc
- cdev_init
- cdev_add
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/uaccess.h>
static int major = 0;
static char kernel_buff[1024];
static struct class *hello_class;
static struct cdev hello_cdev;
#define MIN(a, b) (a < b ? a : b)
static int hello_drv_open(struct inode *node, struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static ssize_t hello_drv_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
int ret;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
ret = copy_to_user(buf, kernel_buff, MIN(1024, size));
return ret;
}
static ssize_t hello_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
int ret;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
ret = copy_from_user(kernel_buff, buf, MIN(1024, size));
return ret;
}
static int hello_drv_release(struct inode *node, struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static struct file_operations hello_drv = {
.owner = THIS_MODULE,
.open = hello_drv_open,
.read = hello_drv_read,
.write = hello_drv_write,
.release = hello_drv_release
};
static int __init hello_init(void)
{
int err;
dev_t devid;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
//major = register_chrdev(0, "hello", &hello_drv);
err = alloc_chrdev_region(&devid, 0, 1, "hello");
major = MAJOR(devid);
if(err < 0) {
printk("register chrdev failed: %d\n", err);
return -1;
}
cdev_init(&hello_cdev, &hello_drv);
cdev_add(&hello_cdev, devid, 1);
hello_class = class_create(THIS_MODULE, "hello_class");
err = PTR_ERR(hello_class);
if(IS_ERR(hello_class)) {
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
unregister_chrdev(major, "hello");
return -1;
}
device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); /* /dev/hello */
return 0;
}
static void __exit hello_exit(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
device_destroy(hello_class, MKDEV(major, 0));
class_destroy(hello_class);
// unregister_chrdev(major, "hello");
cdev_del(&hello_cdev);
unregister_chrdev_region(MKDEV(major, 0), 1);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
三、上机实验
export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
make
root@npi:~/test# insmod hello_drv.ko
root@npi:~/test# ./hello_drv_test
Usage: ./hello_drv_test -w <string>
./hello_drv_test -r
root@npi:~/test# ./hello_drv_test -r hello
APP read :
root@npi:~/test# ./hello_drv_test -w hello
root@npi:~/test# ./hello_drv_test -r
APP read : hello
root@npi:~/test# ls /dev/hello -lh
crw------- 1 root root 244, 0 Mar 17 14:16 /dev/hello
root@npi:~/test# mknod /dev/hello c 244 1
mknod: /dev/hello: File exists
root@npi:~/test# mknod /dev/hello c 244 1
mknod: /dev/hello: File exists
root@npi:~/test# rm /dev/hello
root@npi:~/test# mknod /dev/hello c 244 1
root@npi:~/test# ./hello_drv_test -r
can not open file /dev/hello
此时如果删除/dev/hello节点,然后手动创建一个244 1的节点,用测试程序就无法打开。
因为驱动程序只针对次设备号为0的,并不支持次设备号1