嵌入式Linux驱动笔记(一)------第一个LED驱动程序

时间:2021-11-30 18:57:31

你好!这里是风筝的博客,

欢迎和我一起交流。



//应用程序:
#include <syspes.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
/* leddrvtest on
  * leddrvtest off
  */
int main(int argc, char **argv)
{
	int fd;
	int val = 1;
	fd = open("/dev/led", O_RDWR);
	if (fd < 0)
	{
		printf("can't open!\n");
	}
	if (argc != 2)
	{
		printf("Usage :\n");
		printf("%s <on|off>\n", argv[0]);
		return 0;
	}
	/*如果输入了leddrvtest on*/
	if (strcmp(argv[1], "on") == 0)
		val  = 1;
	else
		val = 0;
	/*写val*/
	write(fd, &val, 4);
	return 0;
}

当我们打开一个文件的时候,需要获得文件的文件描述符,其实就是文件数组下标,一般是通过函数open函数来完成.
fd=open(文件名,打开方式);
例如:fd = open("/dev/led", O_RDWR | O_NONBLOCK);
用来打开一个设备,他返回的是一个整型变量,如果这个值等于-1,说明打开文件出现错误,如果为大于0的值,那么这个值代表的就是文件描述符。
其中:
O_RDONLY 以只读方式打开文件。
O_WRONLY 以只读方式打开文件。
O_RDWR 以只读方式打开文件。
上面三个参数只能选择一个,下面的可以合理的任意组合:
O_APPEND 强制每次写(write)时都加到文件的尾端。
O_CREAT 打开文件,若此文件不存在则创建它。使用此选择项时,需同时说明第三个参数mode,用其说明该新文件的存取许可权位。
O_EXCL 如果同时指定了O_CREAT,而且文件已经存在,则强制open()失败。这可测试一个文件是否存在,如果不存在则创建此文件成为一个原子操作。
O_TRUNC 如果此文件存在,而且为只读或只写成功打开,则将其长度截短为0。
O_NOCTTY 如果p a t h n a m e指的是终端设备,则不将此设备分配作为此进程的控制终端。
O_NONBLOCK 如果p a t h n a m e指的是一个F I F O、一个块特殊文件或一个字符特殊文件,则此选择项为此文件的本次打开操作和后续的I / O操作(open\read\write)设置非阻塞方式。
O_SYNC 使每次w r i t e都等到物理I / O操作完成。



//驱动程序:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>

static struct class *leddrv_class;
static struct class_device	*leddrv_class_dev;

volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;

static int led_drv_open(struct inode *inode, struct file *file)
{
	/* 配置GPF4,5,6为输出 */
	*gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2)));
	*gpfcon |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x1<<(6*2)));
	return 0;
}

static ssize_t led_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
	int val;

	copy_from_user(&val, buf, count); //	copy_to_user();

	if (val == 1)
	{
		// 点灯
		*gpfdat &= ~((1<<4) | (1<<5) | (1<<6));
	}
	else
	{
		// 灭灯
		*gpfdat |= (1<<4) | (1<<5) | (1<<6);
	}
	
	return 0;
}

static struct file_operations led_drv_fops = {
	.owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
	.open   =   led_drv_open,     
	.write	=	led_drv_write,	   
};

int major;
	/*insmod	时调用*/
static int led_drv_init(void)
{
	major = register_chrdev(0, "led_drv", &led_drv_fops); // 注册, 告诉内核

	leddrv_class = class_create(THIS_MODULE, "leddrv");

	leddrv_class_dev = device_create(leddrv_class, NULL, MKDEV(major, 0), NULL, "led"); /* /dev/led */
	
	gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
	gpfdat = gpfcon + 1;

	return 0;
}
	/*rmmod	时调用*/
static void led_drv_exit(void)
{
	unregister_chrdev(major, "led_drv"); // 卸载
	device_destroy(leddrv_class,MKDEV(major, 0));
	class_destroy(leddrv_class);
	iounmap(gpfcon);
}

module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");

  其中:
major = register_chrdev(0, "led_drv", &led_drv_fops);
向内核注册了一个字符设备。
第一个参数是主设备号,0代表内核会动态分配。第二个参数是设备的名字,第三个参数是文件操作指针。
完成注册后,在/proc/devices中可以看到我们的设备。

gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
将一个IO地址空间映射到内核的虚拟地址空间上去,便于访问。
Ioremap函数中,0x56000050为要映射的起始的IO地址;16为要映射空间的大小;这些都视自己芯片手册而定。

MODULE_LICENSE("GPL");
模块的许可证声明。
从2.4.10版本内核开始,模块必须通过MODULE_LICENSE宏声明此模块的许可证,否则在加载此模块时,会收到内核被污染 “kernel tainted” 的警告。

copy_from_user(&val, buf, count);
从用户空间拷贝数据到内核空间,失败返回没有被拷贝的字节数,成功返回0。
由于内核空间与用户空间的内存不能直接互访,因此借助函数copy_to_user()完成用户空间到内核空间的复制,函数copy_from_user()完成内核空间到用户空间的复制。

makefile:
KERN_DIR = /work/system/linux-4.8.17
all:
        make -C $(KERN_DIR) M=`pwd` modules 
clean:
        make -C $(KERN_DIR) M=`pwd` modules clean
        rm -rf modules.order
obj-m   += led_drv.o


make编译后即可生成.ko文件.
这样,insmod led.ko驱动后,输入 lsmod 就会看到刚刚加载上的驱动了。
在应用程序里,输入./leddrvtest on后,就会调用write函数,操作led灯了。