初识Linux驱动,并利用字符设备框架实现Led 驱动


一、Linux驱动

  1. Linux驱动代码结构:
//1、头文件
#include <linux/init.h>
#include <linux/module.h>

//2、驱动入口函数的声明,在内核加载驱动时,执行哪个函数;在内核卸载驱动时,执行哪个函数
//声明:加载时的入口声明
module_init(hello_init);
//声明:卸载时的入口声明
module_exit(hello_exit);

//3、加载函数、卸载函数的实现
//加载函数的实现:当内核加载驱动(内核执行这个驱动时,就会调用的函数)
static int __init hello_init(void)
{
	return 0;
}

//卸载函数的实现:当内核卸载驱动(内核删除这个驱动时,就会调用的函数)
static void __exit hello_exit(void)
{
}

//4、协议选择GPL
MODULE_LICENSE("GPL");
  1. 利用Makefile 编译
KERNLE_PATH=/home/ubuntu/code/kernel/linux-3.14

APP=led_test

obj-m=led_drv.o

all:
	make modules -C $(KERNLE_PATH) M=$(shell pwd)
	/home/ubuntu/tools/gcc-4.6.4/bin/arm-none-linux-gnueabi-gcc $(APP).c -o $(APP)

install:
	cp *.ko $(APP) /nfsdir/rootfs
  1. Linux内核如何使用模块驱动

加载:insmod 驱动程序路径.ko
查看:lsmod //查看当前已经加载的驱动模块
卸载:rmmod 驱动程序名


二、字符设备驱动

(一) 字符设备驱动的要素:

  • 必须要有一个设备号,用于在内核中的众多设备驱动进行区分
  • 必须要有一个设备文件,用户必须知道设备驱动对应的设备节点(设备文件)
  • 驱动对设备的操作,与应用程序中的系统调用关联,其实就是文件操作

应用空间调用open、read、write、ioctl函数,实际在驱动中调用对应的xxx_open、xxx_read等函数

(二)实现字符设备框架

  1. 创建设备号
int register_chrdev(unsigned int major,const char *name,const struct file_operations *fops)
  1. 创建一个设备节点(设备文件)

a、手动创建

mknod	设备节点名	设备类型	主设备号	次设备号

b、自动创建(通过udev/mdev机制)

//创建设备节点
struct class * class_create(owner,name);
//创建设备文件
struct device *device_create(struct class *class, struct device *parent,dev_t devt,void *drvdata, const char *fmt, ...)
  1. 在驱动中实现文件io接口功能(与应用关联)
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 (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
		ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
		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 (*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 **);
		long (*fallocate)(struct file *file, int mode, loff_t offset,
			  loff_t len);
		int (*show_fdinfo)(struct seq_file *m, struct file *f);
	};//函数指针的集合,

每个函数指针赋值为函数地址,就代表当应用程序调用对应的文件io函数时,驱动就执行函数指针赋值的对应函数

  1. 应用程序操作驱动,应用程序调用文件io函数去控制驱动
  2. 应用和驱动传递数据
read、write
copy_to_user
copy_from_user
  1. 驱动控制硬件,控制外设,其实就是控制地址,通过地址往寄存器写入、读出控制

实现一个简单的Led 驱动:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define GPX1CON 0x11000c20
#define GPX1DAT 0x11000c24

//设计结构体类型来表示整个驱动对象(描述设备驱动信息)
struct led_drv
{
		unsigned int major;//主设备号
		dev_t devno;//设备号
		struct class * cls;//设备文件信息结构体
		struct device* dev;//设备文件信息
		unsigned int * gpx1con;//设备驱动映射的地址
		unsigned int * gpx1dat;
};
//定义全局设备对象(存储信息)
struct led_drv  led;

//驱动与应用程序函数关联
int led_open(struct inode * inode, struct file * file)
{
	printk("led_open\n");
	return 0;
}

int led_close(struct inode * inode, struct file * file)
{
	printk("led_close\n");
	return 0;
}

ssize_t led_write(struct file * file, const char __user * buf, size_t size, loff_t * ops)
{
	int num = 0;
	void *p = copy_from_user(&num, buf, size);
	printk("addr is = %p\n", p);
	
	if (num == 1)
	{
		*(led.gpx1dat) |= 1;
	}
	else
	{
		*(led.gpx1dat) &= ~1;
	}
	
	return 0;
}

const struct file_operations fops = {
	.open = led_open,
	.release = led_close,
	.write = led_write,
};

static int __init led_init(void)
{
	//申请设备号
	led.major = 250;
	led.devno = led.major << 20 | 0;
	int ret = -1;
	ret = register_chrdev(led.major, "led drv", &fops);
	if (ret < 0)
	{
		printk("register devno error\n");
		goto err_1;
	}

	//创建设备文件
	led.cls = class_create(THIS_MODULE, "led cls");
	if (IS_ERR(led.cls))
	{
		printk("class create error\n");
		goto err_2;
	}
	
	led.dev = device_create(led.cls, NULL, led.devno, NULL, "led");
	if (IS_ERR(led.dev))
	{
		printk("device create erroe\n");
		goto err_3;
	}

	//硬件初始化
	led.gpx1con = ioremap(GPX1CON, 4);
	if (led.gpx1con == NULL)
	{
		printk(
"gpx1con ioremap error\n");
		goto err_4;
	}
	
	led.gpx1dat = ioremap(GPX1DAT, 4);
	if (led.gpx1dat == NULL)
	{
		printk(
"gpx1dat ioremap error\n");
		goto err_5;
	}
	
	//硬件寄存器初始化
	*(led.gpx1con) = *(led.gpx1con) & ~(0xf) | 0x1;
	*(led.gpx1dat) |= 1;
	
	return 0;

err_5:
	iounmap(led.gpx1con);

err_4:
	device_destroy(led.cls, led.devno);

err_3:
	class_destroy(led.cls);
	
err_2:
	unregister_chrdev(led.major, "led drv");
	
err_1:
	return -1;
}

static void __exit led_exit(void)
{
	//卸载驱动内容
	//1、释放映射地址
	iounmap(led.gpx1con);
	iounmap(led.gpx1dat);

	//2、释放设备文件
	device_destroy(led.cls,led.devno);
	//3、释放设备文件结构体
	class_destroy(led.cls);

	//4、释放设备号
	unregister_chrdev(led.major,"led drv");
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

应用程序:

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

int main()
{
	int fd = open("/dev/led",O_WRONLY);
	int i = 0;
	int a;
	while(i < 30)
	{
		a = 1;
		write(fd,&a,4);
		sleep(1);

		a = 0;
		write(fd,&a,4);
		sleep(1);

		i++;
	}
	close(fd);
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值