Linux设备驱动 | LED字符设备驱动实验

Linux字符设备驱动

Linux实现了一套字符设备驱动框架,编写字符设备驱动就按照框架进行编写。
字符设备驱动结构框图:
在这里插入图片描述
图片来自 – 《Linux设备驱动开发详解》

最很重要的就是实现struct file_operations该结构中的函数。
struct file_operations 结构体定义了字符设备驱动提供给虚拟文件系统的接口函数

struct 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 *);
	int (*iterate_shared) (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 (*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 (*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
	ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
			loff_t, size_t, unsigned int);
	int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,
			u64);
	ssize_t (*dedupe_file_range)(struct file *, u64, u64, struct file *,
			u64);
};

结构体中的函数指针和系统的system call 名字很类似

LED字符设备驱动实验

1、查看手册和原理图得到可操作LED连接GPIO组的相关寄存器

#define  CCM_CCGR1                0x20C406C                           
#define  MUX_PAD_GPIO1_IO04       0x20E006C    

#define  GPIO1_DR                 0x209C000
#define  GPIO1_GDIR               0x209C004

2、定义struct file_operations led_drv结构体并填充成员

static struct file_operations led_drv = {
	.owner	 = THIS_MODULE,
	.open    = led_drv_open,
	.read    = led_drv_read,
	.write   = led_drv_write,
	.release = led_drv_close,
};

.owner成员值一般设置为THIS_MODULE

3、实现file_operations结构填充的函数

static unsigned int __iomem *_CCM_CCGR1;
static unsigned int __iomem *_MUX_PAD_GPIO1_IO04;
static unsigned int __iomem *_GPIO1_DR;
static unsigned int __iomem *_GPIO1_GDIR;

static int major = 0;    
static struct class *led_class;

static int led_drv_open (struct inode *node, struct file *file)
{
    int val;
	  
	 printk("open %s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	
    _CCM_CCGR1 = ioremap(CCM_CCGR1, 4);                        //重映射物理地址为虚拟地址           
	_MUX_PAD_GPIO1_IO04 = ioremap(MUX_PAD_GPIO1_IO04, 4);
    _GPIO1_DR = ioremap(GPIO1_DR, 4);
    _GPIO1_GDIR = ioremap(GPIO1_GDIR, 4);  

    val = ioread32(_CCM_CCGR1);
    val &= ~(3 << 26);   
    val |= (3 << 26);   
    iowrite32(val, _CCM_CCGR1);    

    val = 0;
    val = ioread32(_MUX_PAD_GPIO1_IO04);     
    val &= ~(0xf << 0);    
    val |= (0x5 << 0);  
    iowrite32(val, _MUX_PAD_GPIO1_IO04);   

    val = 0;
    val = ioread32(_GPIO1_GDIR);     
    val |= (0x1 << 4); 
    iowrite32(val, _GPIO1_GDIR);   

	return 0;
}

static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	printk("read %s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

/* write(fd, &val, 1); */
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	int err;
	char status;
  	int val = 0;
	
	printk("write %s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	err = copy_from_user(&status, buf, 1);

	if (status)
    {
        val &= ~(1 << 4);
        iowrite32(val, _GPIO1_DR);   
    }
    else
    {
        val |= (1 << 4);
        iowrite32(val, _GPIO1_DR);       
    }
	
	return 1;
}

static int led_drv_close (struct inode *node, struct file *file)
{
	printk("close %s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    iounmap(_CCM_CCGR1);
    iounmap(_MUX_PAD_GPIO1_IO04);
    iounmap(_GPIO1_DR);
    iounmap(_GPIO1_GDIR);
	return 0;
}

因为有MMU存在,Linux进程中使用的是虚拟地址,需要将寄存器的物理地址转换为内核能使用虚拟地址

重映射物理地址为虚拟地址 :

_CCM_CCGR1 = ioremap(CCM_CCGR1, 4);                        //重映射物理地址为虚拟地址           
_MUX_PAD_GPIO1_IO04 = ioremap(MUX_PAD_GPIO1_IO04, 4);
_GPIO1_DR = ioremap(GPIO1_DR, 4);
_GPIO1_GDIR = ioremap(GPIO1_GDIR, 4);  

在led设备关闭操作中对虚拟地址解除映射:

 iounmap(_CCM_CCGR1);
 iounmap(_MUX_PAD_GPIO1_IO04);
 iounmap(_GPIO1_DR);
 iounmap(_GPIO1_GDIR);

读寄存器:

val = ioread32(_CCM_CCGR1);

写寄存器:

iowrite32(val, _CCM_CCGR1);    

用户空间应用程序通过调用system call时通过swi从用户空间陷入到内核空间进而调用了驱动中提供的函数。
而用户空间和内核空间的内存是隔离的,所以应用程序完内核写如数据需要需要进行数据的拷贝传递
将用户空间的程序拷贝到内核空间:

err = copy_from_user(&status, buf, 1);

4、驱动的入口/出口函数


/* 驱动加载函数 */
static int __init led_init(void)
{
  int err;
    major = register_chrdev(0, "my_led", &led_drv);

    led_class = class_create(THIS_MODULE, "my_led_class");
	err = PTR_ERR(led_class);
	if (IS_ERR(led_class)) {
		unregister_chrdev(major, "my_led"); 
		return -1;
	}
    device_create(led_class, NULL, MKDEV(major, 0), NULL, "my_led"); 
   	printk("init %s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    return 0;
}

/* 驱动卸载函数 */
static void __exit led_exit(void)
{
    printk("exit %s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    device_destroy(led_class, MKDEV(major, 0));           //销毁设备节点
	class_destroy(led_class);                             //销毁跟设备节点相关的类       
	unregister_chrdev(major, "my_led");                   //注销设备
}

module_init(led_init);
module_exit(led_exit);

MODULE_AUTHOR("Ares");
MODULE_LICENSE("GPL");

注册字符设备驱动:

major = register_chrdev(0, "my_led", &led_drv);  //向系统申请设备号 

创建管理LED设备驱动的类:

led_class = class_create(THIS_MODULE, "my_led_class");

加载驱动自动创建设备节点:

device_create(led_class, NULL, MKDEV(major, 0), NULL, "my_led");   //创建了设备节点,用户空间才能通过设备节点进行操作硬件

在驱动入口函数中注册设备、创建设备节点等,相应的需要在驱动卸载的出口函数中进行相反的操作。

通过主设备号和此设备号合并得到设备号:

MKDEV(major, 0)  //kdev_t.h头文件中定义

5、加载/卸载驱动

加载驱动:

debian@npi:~/nfs_rootfs$ sudo insmod led.ko

加载驱动 后/dev/目录下出现了设备节点:

debian@npi:~/nfs_rootfs$ ls /dev/my_led
/dev/my_led

/sys/class/目录下生成了类对应的目录:
在这里插入图片描述

卸载驱动:

sudo rmmod led.ko

6、编写应用程序测试驱动


#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

/*
 * ./ledtest /dev/dev_name on
 * ./ledtest /dev/dev_name off
 */
int main(int argc, char **argv)
{
	int fd;
	char status;
	
	if (argc != 3) 
	{
		printf("Usage: %s <dev> <on | off>\n", argv[0]);
		return -1;
	}

	fd = open(argv[1], O_RDWR);
	if (fd == -1)
	{
		printf("can not open file %s\n", argv[1]);
		return -1;
	}

	if (0 == strcmp(argv[2], "on"))
	{
		status = 1;
		write(fd, &status, 1);
	}
	else
	{
		status = 0;
		write(fd, &status, 1);
	}
	
	close(fd);
	
	return 0;
}

执行应用程序操作LED:

debian@npi:~/nfs_rootfs$ sudo ./led_test /dev/my_led on
debian@npi:~/nfs_rootfs$ sudo ./led_test /dev/my_led off
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
字符设备驱动 按字节来访问的设备驱动 它被组织为一组完成不同任务的函数集合 通过这些函数使得Linux字符设备操作犹如文件一样 从应用程序的角度看,硬件设备是一个设备文件 对于应用程序工程师来说,使用设备文件与使用普通文件的方法是相同的。 块设备驱动 以块为单位接受输入和返回输出 Linux允许块设备传送任意数目字节的数据块 Linux对于I/O请求有对应的缓冲区,可以选择响应顺序 块设备可以被随机访问 字符设备驱动程序开发流程 设备字符设备驱动的重要数据结构介绍 字符设备的注册流程 字符设备相关操作 创建设备文件 编写驱动程序程序 主设备号 –前12位 表示与设备文件相关联的驱动程序 确定设备类型 次设备号—后20位 表示被驱动程序用来辨别操作的是哪个设备 区分同类设备 file_operations 把系统调用和驱动程序关联起来的关键数据结构 结构的每一个成员都对应着一个系统调用 读取file_operation中相应的函数指针,接着把控制权转交给函数,从而完成了Linux设备驱动程序的工作 中定义的是三个结构体非常重要 file_operations file 代表一个打开的文件 系统中每个打开的文件在内核空间都有一个关联的Struct File 它由内核在打开文件时创建,在文件关闭后释放 inode 用来记录文件的物理信息 一个文件可以打开多个file结构, 但只有一个inode结构 module_init(leddev_init); module_exit(leddev_exit); 创建设备文件 mknod

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

欲盖弥彰1314

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值