本实验目的为用户可以通过应用层程序输入on/off来控制led灯的亮灭。
1.前置知识——简单字符设备的实现
1.1 设备号
1.1.1 主设备号与次设备号
对字符设备的访问是通过文件系统内的设备名称进行的。通常而言,主设备号标识设备对应的驱动程序。次设备号则由内核使用,用于正确确定设备文件所指的设备。你可以通过在/dev目录下利用ls -l查看设备的主次设备号。
dev_t类型用来保存设备编号。它是一个32位的数,其中12位标识主设备号,20位标识次设备号。
MAJOR(dev_t dev);//获取主设备号
MINOR(dev_t dec);//获取次设备号
MKDEV(int major, int minor);//由主次设备号得到设备号
1.1.2 分配和释放设备编号
在建立一个字符设备之前,我们的驱动程序首先要做的事情就是获得一个或者多个设备编号。
int register_chrdev_region(dev_t first, unsigned int count, char *name);//明确知道设备号选它
int alloc_chrdev_region(dev_t dev, unsigned int firstminor, unsigned count, char *name);//不知道就选择它,由内核自动分配主设备号
/*firstminor是要使用的被请求的第一个次设备号一般为0,count是所请求的连续设备编号的个数,name则是设备名称,你会在用户层的应用函数中使用到它*/
1.2 重要数据结构
1.2.1 file_operation
用于建立设备编号与驱动程序操作的连接。被初始化如下。
struct file_operations chr_ops = {
.owner = THIS_MODULE,
.open = chr_open,
.read = chr_read
};//将其中对应的操作绑定到对应函数上
1.2.2 file
代表了一个打开的文件。它由内核在open时创建,并且传递给在该文件上进行操作的所有函数,直到close函数。
1.2.3 inode
内核用inode结构在内部表示文件。对于一个文件来说,可能会有多个表示打开的文件描述符的file结构,但是它们都会指向一个inode结构。该结构中对编写驱动程序代码有用的两个字段如下。
dev_t i_rdev; //该字段包含了真正的设备编号
struct cdev *i_cdev;//struct cdev表示字符设备的内核的内部结构。
1.3 字符设备的注册
当用户想要在运行时获取一个独立的cdev结构,应该如此编写代码
/*首先定义出struct cdev结构体,file_operation结构与设备编号*/
static struct cdev chr_dev;//内核内部使用cdev结构来表示字符设备,内核调用设备前,需要先注册该结构
struct file_operations chr_ops = {
.owner = THIS_MODULE,
.open = chr_open,
.read = chr_read
};
static dev_t ndev;//用来保存设备编号,MAJOR获得主设备号,MINOR获得次设备号
/*然后利用cdev_init函数完成对cdev结构成员的初始化,建立cdev与file_operation的联系*/
cdev_init(&chr_dev, &chr_ops);
/*结构设置好后,利用下面的调用告诉内核该结构的信息*/
alloc_chrdev_region(&ndev, 0, 1, "chr_dev");//动态申请设备号
cdev_add(&chr_dev, ndev, 1);
cdev_add函数可能会失败,一定要对返回值进行检查。
1.4 简单字符设备实现(代码)
open和read函数没有实质内容,可以自己随便写
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/init.h>
//执行顺序:demo_init
static struct cdev chr_dev;
static dev_t ndev;//用来保存设备编号,MAJOR获得主设备号,MINOR获得次设备号
static int chr_open(struct inode *nd, struct file *filp)
{
int major;
int minor;
major = MAJOR(nd->i_rdev); //inode中的i_rdev包含了真正的设备编号
minor = MINOR(nd->i_rdev);
printk("chr_open, major = %d, minor = %d\n", major, minor);
return 0;
}
static ssize_t chr_read(struct file *filp, char __user *u, size_t sz, loff_t *of)
{
printk("chr_read process!\n");
return 0;
}
struct file_operations chr_ops = {
.owner = THIS_MODULE,
.open = chr_open,
.read = chr_read
};//将其中对应的操作绑定到对应函数上
static int demo_init(void)
{
int ret;
cdev_init(&chr_dev, &chr_ops);//静态内存定义初始化
ret = alloc_chrdev_region(&ndev, 0, 1, "chr_dev");//由内核自动分配主设备号
if (ret < 0)
return ret;
printk("demo_init(): major = %d, minor = %d\n", MAJOR(ndev), MINOR(ndev));
ret = cdev_add(&chr_dev, ndev, 1);
if (ret < 0)
return ret;
return 0;
}
static void demo_exit(void)
{
printk("demo_exit process!\n");
cdev_del(&chr_dev);
unregister_chrdev_region(ndev, 1);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
完成编写后,将编译(一定要用开发板的内核来编译)得到的ko文件传入开发板,使用insmod xx.ko,将该模块加载入内核。
/*应用层程序,打开设备文件,并且测试定义的read和close文件,可以通过dmesg命令查看是否成功执行*/
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#define CHAR_DEV_NAME "/dev/chr_dev"
int main(void)
{
int ret;
int fd;
char buf[32];
fd = open(CHAR_DEV_NAME, O_RDONLY | O_NDELAY);
if (fd < 0) {
printf("open failed!\n");
return -1;
}
read(fd, buf, 32);
close(fd);
return 0;
}
编译时应当使用开发板的编译环境,否则无法运行。
2. 将gpio申请和字符设备注册结合
2.1 补充知识-read与write的实现核心
unsigned long copy_to_user(void __user *to, const void *from, unsigned long count);
unsigned long copy_from_user(void *to, const void __user *from, unsigned long count);
read使用copy_to_user,从设备拷贝数据到用户空间。
write使用copy_from_user,从用户空间拷贝数据到设备。
2.2 补充知识-建立设备(个人理解,不一定正确。。)
1.手动:mknod /dev/dev_name(你的设备名) c(字符设备) major(主设备号) minor(次设备号)
2.自动: 现在/sys/class下创建目录 class_create()
然后在/dev下创建设备 device_create()
2.3 dts文件描述与代码
2.3.1 dts文件
leds {
status = "okay";
compatible = "gpio-myled";
gpio-myled = <&gpio0 RK_PB2 GPIO_ACTIVE_HIGH>;
};
2.3.2 驱动程序
#include <linux/io.h>
#include <linux/of.h>
#include <linux/fs.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
static int GPIO_PIN;
static int majorNumber;
static const char *CLASS_NAME = "led_control_class";/*Class 名称,对应/sys/class*/
static const char *DEVICE_NAME = "led_control_demo";/*Device 名称,对应/dev*/
static struct cdev gpio_dev;
static dev_t ndev;
static int gpio_status;
static char recv_msg[20];
static struct class *gpio_control_class;
static struct device *gpio_control_device;
static int gpio_control_open(struct inode *node, struct file *file);
static ssize_t gpio_control_read(struct file *file, char *buf, size_t len, loff_t *offset);
static ssize_t gpio_control_write(struct file *file, const char *buf, size_t len, loff_t *offset);
static int gpio_control_release(struct inode *node, struct file *file);
/*File opertion 结构体,我们通过这个结构体建立应用程序到内核之间操作的映射*/
static struct file_operations file_oprts = {
.open = gpio_control_open,
.read = gpio_control_read,
.write = gpio_control_write,
.release = gpio_control_release,
};
struct gpio_myled_info
{
int myled_gpio;
int gpio_enable_value;
};
static int gpio_myled_probe(struct platform_device *pdev)
{
enum of_gpio_flags flag;
struct gpio_myled_info *gpio_info;
struct device_node *gpio_myled_node = pdev->dev.of_node;
printk("Myled GPIO Test Program Probe\n");
gpio_info = devm_kzalloc(&pdev->dev, sizeof(struct gpio_myled_info *),GFP_KERNEL);
if (!gpio_info)
{
dev_err(&pdev->dev, "devm_kzalloc failed!\n");
return -ENOMEM;
}
GPIO_PIN = of_get_named_gpio_flags(gpio_myled_node, "gpio-myled", 0, &flag);
if (!gpio_is_valid(GPIO_PIN))
{
dev_err(&pdev->dev, "gpio-myled: %d is invalid!\n", GPIO_PIN);
return -ENODEV;
}
if (gpio_request(GPIO_PIN, "gpio-myled"))
{
dev_err(&pdev->dev, "gpio-myled: %d request failed!\n", GPIO_PIN);
gpio_free(GPIO_PIN);
return -ENODEV;
}
gpio_info->myled_gpio = GPIO_PIN;
gpio_info->gpio_enable_value = (flag == OF_GPIO_ACTIVE_LOW) ? 0:1;
gpio_direction_output(gpio_info->myled_gpio, gpio_info->gpio_enable_value);
printk("Myled gpio putout\n");
return 0;
}
static struct of_device_id myled_match_table[] = {
{
.compatible = "gpio-myled",},
{
},
};
static struct platform_driver gpio_myled_driver = {
.driver = {
.name = "gpio-myled",
.owner = THIS_MODULE,
.of_match_table = myled_match_table,
},
.probe = gpio_myled_probe,
};
/*当用户打开这个设备文件时,调用这个函数*/
static int gpio_control_open(struct inode *node, struct file *file)
{
printk(KERN_ALERT "GPIO init\n");
return 0;
}
/*当用户试图从设备空间读取数据时,调用这个函数*/
static ssize_t gpio_control_read(struct file *file, char *buf, size_t len, loff_t *offset)
{
int cnt = 0;/*将内核空间的数据copy到用户空间*/
cnt = copy_to_user(buf, &gpio_status, 1);
if (cnt == 0) {
return 0;
} else {
printk(KERN_ALERT "ERROR occur when reading!!\n");
return -EFAULT;
}
return 1;
}
/*当用户往设备文件写数据时,调用这个函数*/
static ssize_t gpio_control_write(struct file *file, const char *buf, size_t len, loff_t *offset)
{
/*将用户空间的数据copy到内核空间*/
int cnt = copy_from_user(recv_msg, buf, len);
if (cnt == 0) {
if (memcmp(recv_msg, "on", 2) == 0) {
printk(KERN_INFO "gpio ON!\n");
gpio_set_value(GPIO_PIN, 1);
gpio_status = 1;
} else if (memcmp(recv_msg, "off", 3) == 0) {
printk(KERN_INFO "gpio OFF!\n");
gpio_set_value(GPIO_PIN, 0);
gpio_status = 0;
}
} else {
printk(KERN_ALERT "ERROR occur when writing!!\n");
return -EFAULT;
}
return len;
}
/*当用户打开设备文件时,调用这个函数*/
static int gpio_control_release(struct inode *node, struct file *file)
{
printk(KERN_INFO "Release!!\n");
return 0;
}
static int gpio_myled_init(void)
{
platform_driver_register(&gpio_myled_driver);
printk(KERN_ALERT "Driver init\r\n");/*注册一个新的字符设备,返回主设备号*/
cdev_init(&gpio_dev, &file_oprts);
if (alloc_chrdev_region(&ndev, 0, 1, DEVICE_NAME) < 0) {
printk(KERN_ALERT "Register failed!!\r\n");
return 0;
}
if (cdev_add(&gpio_dev, ndev, 1) < 0) {
printk("add wrong!\n");
return 0;
}
majorNumber = MAJOR(ndev);
printk(KERN_ALERT "Registe success,major number is %d\r\n", majorNumber);
/*以CLASS_NAME创建一个class结构,这个动作将会在/sys/class目录创建一个名为CLASS_NAME的目录*/
gpio_control_class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(gpio_control_class)) {
unregister_chrdev_region(ndev, 1);
return PTR_ERR(gpio_control_class);
}
/*以DEVICE_NAME为名,参考/sys/class/CLASS_NAME在/dev目录下创led建一个设备:/dev/DEVICE_NAME*/
gpio_control_device = device_create(gpio_control_class, NULL, MKDEV(majorNumber, 0), NULL, DEVICE_NAME);
if (IS_ERR(gpio_control_device)) {
class_destroy(gpio_control_class);
unregister_chrdev_region(ndev, 1);
return PTR_ERR(gpio_control_device);
}
printk(KERN_ALERT "gpio_control device init success!!\r\n");
return 0;
}
module_init(gpio_myled_init);
static void gpio_myled_exit(void)
{
device_destroy(gpio_control_class, MKDEV(majorNumber, 0));
class_unregister(gpio_control_class);
class_destroy(gpio_control_class);
unregister_chrdev_region(ndev, 1);
platform_driver_unregister(&gpio_myled_driver);
}
module_exit(gpio_myled_exit);
MODULE_AUTHOR("Pato");
MODULE_LICENSE("GPL");
2.3.3 测试代码
#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
static char buf[256] = {
1};
int main(int argc,char *argv[])
{
int fd = open("/dev/led_control_demo",O_RDWR);
if(fd < 0)
{
perror("Open file failed!!!\r\n");
return -1;
}
printf("Please input <on> or <off>:\n");
scanf("%s",buf);
if(strlen(buf) > 3){
printf("Ivalid input!\n");
}
else
{
int ret = write(fd,buf,strlen(buf));
if(ret < 0){
perror("Failed to write!!");
}
}
close(fd);
return 0;
}
文章评论