一、字符设备驱动框架
字符设备驱动的编写主要就是驱动对应的open、close、read。。。其实就是file_operations结构体的成员变量的实现。
二、驱动模块的加载与卸载
1、Linux驱动程序有两种存在形式:
编译到kernel里面,也就是zImage
编译为模块,.ko。
TIP:
1、编译驱动的时候需要用到linux内核源码!因此要解压缩linux内核源码,编译linux内核源码。得到zImage和.dtb。需要使用编译后的到的zImage和dtb启动系统
2、从SD卡启动,SD卡烧写了uboot。uboot通过tftp从ubuntu里面获取zimage和dtb,rootfs也是通过nfs挂在。
3、设置bootcmd和bootargs
setenv bootargs root=/dev/nfs nfsroot=192.168.3.112:/rootfs rw console=ttySAC2,115200 init=/linuxrc
setenv bootcmd tftp 41000000 uImage ; tftp 42000000 exynos4412-fs4412.dtb ; bootm 41000000 - 42000000
4、将编译出来的.ko文件放到根文件系统里面。
加载驱动会用到加载命令:insmod,modprobe。
移除驱动使用命令rmmod。对于一个新的模块使用modprobe加载的时候需要先调用一下depmod命令。
5,驱动模块加载成功以后可以使用lsmod查看一下。
6,卸载模块使用rmmod命令
三、字符设备的注册与注销
1、我们需要向系统注册一个字符设备,使用函数register_chrdev。
2、卸载驱动的时候需要注销掉前面注册的字符设备,使用函数unregister_chrdev,注销字符设备。
四、设备号
1,Linux内核使用dev_t。(用来保存设备号)
typedef __kernel_dev_t dev_t;
typedef __u32 __kernel_dev_t;
typedef unsigned int __u32;
2、Linux内核将设备号分为两部分:
主设备号和次设备号。
主设备号占用前12位,次设备号占用低20位。(根据不同内核占用位数不同)
3、设备号的操作函数,
a 、用宏从dev_t获取主设备号和次设备号
MAJOR(dev_t),
MINOR(dev_t)。
b、用函数把主设备号和次设备号构成dev_t,
MKDEV(major,minor)
五、file_operations的具体实现
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iterate) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*mremap)(struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long,unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif};
主要注意ioctl,read,write,open,release函数实现使用
六、字符设备驱动框架的搭建
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
static int major = 200;
static int minor=0;
static dev_t devno;
static struct class *cls;
static struct device *test_device;
/* 寄存器物理地址 */
#define GPX2CON 0x11000c40
#define GPX2DAT 0x11000c44
#define GPX1CON 0x11000c20
#define GPX1DAT 0x11000c24
#define GPF3CON 0x114001e0
#define GPF3DAT 0x114001e4
/* 地址映射后的虚拟地址指针 */
static int *pgpx2con ;
static int *pgpx2dat;
static int *pgpx1con ;
static int *pgpx1dat;
static int *pgpf3con ;
static int *pgpf3dat;
void fs4412_led_on(int num)
{
switch(num)
{
case 1: writel(readl(pgpx2dat) |(0x1<<7), pgpx2dat); break;
case 2: writel(readl(pgpx1dat) |(0x1<<0), pgpx1dat); break;
case 3: writel(readl(pgpf3dat) |(0x1<<4), pgpf3dat); break;
case 4: writel(readl(pgpf3dat) |(0x1<<5), pgpf3dat); break;
default:
fs4412_led_off(1);
fs4412_led_off(2);
fs4412_led_off(3);
fs4412_led_off(4);
break;
}
}
void fs4412_led_off(int num)
{
switch(num)
{ case 1: writel(readl(pgpx2dat) &(~(0x1<<7)), pgpx2dat); break;
case 2: writel(readl(pgpx1dat)&(~(0x1<<0)), pgpx1dat); break;
case 3: writel(readl(pgpf3dat) &(~(0x1<<4)), pgpf3dat); break;
case 4: writel(readl(pgpf3dat) &(~(0x1<<5)), pgpf3dat); break;
}
}
static int led_open (struct inode *inode, struct file *filep)
{
fs4412_led_off(1);
fs4412_led_off(2);
fs4412_led_off(3);
fs4412_led_off(4);
return 0;
}
static int led_release(struct inode *inode, struct file *filep)
{
fs4412_led_off(1);
fs4412_led_off(2);
fs4412_led_off(3);
fs4412_led_off(4);
return 0;
}
static ssize_t led_read(struct file *filep, char __user *buf, size_t len, loff_t *pos)
{
return 0;
}
static ssize_t led_write(struct file *filep, const char __user *buf, size_t len, loff_t *pos)
{
int led_num;
if(len !=4)
{
return -EINVAL; }
if(copy_from_user(&led_num,buf,len))
return -EFAULT;
fs4412_led_on(led_num);
printk("led_num =%d \n",led_num);
return 0;
}
//编写fop结构体
static struct file_operations hello_ops={
.open = led_open,
.release = le/d_release,
.read = led_read,
.write = led_write,
};
//编写加载函数
static void fs4412_led_init(void)
{
pgpx2con = ioremap(GPX2CON,4);
pgpx2dat = ioremap(GPX2DAT,4);
pgpx1con = ioremap(GPX1CON,4);
pgpx1dat =ioremap(GPX1DAT,4);
pgpf3con = ioremap(GPF3CON,4);
pgpf3dat =ioremap(GPF3DAT,4);
writel((readl(pgpx2con)& ~(0xf<<28)) |(0x1<<28),pgpx2con) ;
writel((readl(pgpx1con)& ~(0xf<<0)) |(0x1<<0),pgpx1con) ;
writel((readl(pgpf3con)& ~(0xff<<16)) |(0x11<<16),pgpf3con) ;
}
//编写卸载函数
static int led_init(void)
{
int ret;
devno = MKDEV(major,minor); //存储设备号
ret = register_chrdev(major,"led",&hello_ops);
cls = class_create(THIS_MODULE, "myclass");
if(IS_ERR(cls))
{
unregister_chrdev(major,"led");
return -EBUSY;
}
test_device = device_create(cls,NULL,devno,NULL,"led");//自动创建设备节点
if(IS_ERR(test_device))
{
class_destroy(cls);
unregister_chrdev(major,"led");
return -EBUSY;
}
fs4412_led_init();
return 0;
}
void fs4412_led_unmap(void)
{
iounmap(pgpx2con);
iounmap(pgpx2dat );
iounmap(pgpx1con);
iounmap(pgpx1dat );
iounmap(pgpf3con );
iounmap(pgpf3dat );
}
static void led_exit(void)
{
fs4412_led_unmap();
device_destroy(cls,devno); class_destroy(cls);
unregister_chrdev(major,"led");
printk("led_exit \n");
}
MODULE_LICENSE("GPL");//指定许可
module_init(led_init);//指定加载函数
module_exit(led_exit);//指定卸载函数
七、测 试
1、加载驱动。
modprobe led.ko
2,进入/dev查看设备文件,
lsmod。/dev/chrdevbase。
3、测试