雄安专业网站建设,地产项目合作开发网,网站建设顺序,无锡网站建设 微信公众号一、原子操作实验 这节使用原子操作来实现对 LED 设备的互斥访问#xff0c;也就是只有一个应用程序能使用 LED。 1.1 实验程序编写 因为是 12 章已经修改了设备树#xff0c;所以这里暂时不用修改。 在 /linux/atk-mpl/Drivers 该目录下创建 7_atomic 子目录#xff0c;并且…
一、原子操作实验 这节使用原子操作来实现对 LED 设备的互斥访问也就是只有一个应用程序能使用 LED。 1.1 实验程序编写 因为是 12 章已经修改了设备树所以这里暂时不用修改。 在 /linux/atk-mpl/Drivers 该目录下创建 7_atomic 子目录并且把 5_gpioled 里面的 gpioled.c 文件复制到 7_atomic 子目录下并重命名为 atomic.c还在改子目录下创建 Vscode 工作区。首先先编写 atomic.c 程序
#include linux/types.h
#include linux/kernel.h
#include linux/delay.h
#include linux/ide.h
#include linux/init.h
#include linux/module.h
#include linux/errno.h
#include linux/gpio.h
#include linux/cdev.h
#include linux/device.h
#include linux/of.h
#include linux/of_address.h
#include linux/of_gpio.h
#include asm/mach/map.h
#include asm/uaccess.h
#include asm/io.h#define GPIOLED_CNT 1 /* 设备号个数 */
#define GPIOLED_NAME gpioled /* 名字 */
#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 *//* gpioled设备结构体 */
struct gpioled_dev{dev_t devid; /* 设备号 */struct cdev cdev; /* cdev */struct class *class; /* 类 */struct device *device; /* 设备 */int major; /* 主设备号 */int minor; /* 次设备号 */struct device_node *nd; /* 设备节点 */int led_gpio; /* led所使用的GPIO编号 */ // 此成员变量保存 LED 等所使用的 GPIO 编号atomic_t lock; // 原子变量
};struct gpioled_dev gpioled; /* led设备 *//** description : 打开设备* param - inode : 传递给驱动的inode* param - filp : 设备文件file结构体有个叫做private_data的成员变量* 一般在open的时候将private_data指向设备结构体。* return : 0 成功;其他 失败*/
static int led_open(struct inode *inode, struct file *filp)
{/* 通过判断原子变量的值来检查LED有没有被别的应用使用 */if (!atomic_dec_and_test(gpioled.lock)) // 给lock减如果结果为返回真否则为假这里用了取反所以这里是不为为真这里只有和减一说明要么要么这里判断lock为才往下走{ // 相反lock为减一后为条件不满足不执行这就是判断依据atomic_inc(gpioled.lock); // 给lock加 /* 小于0的话就加1,使其原子变量等于0 */retrun -EBUSY; /* LED被使用返回忙 */}filp-private_data gpioled; /* 设置私有数据 */return 0;
}/** description : 从设备读取数据 * param - filp : 要打开的设备文件(文件描述符)* param - buf : 返回给用户空间的数据缓冲区* param - cnt : 要读取的数据长度* param - offt : 相对于文件首地址的偏移* return : 读取的字节数如果为负值表示读取失败*/
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{return 0;
}/** description : 向设备写数据 * param - filp : 设备文件表示打开的文件描述符* param - buf : 要写给设备写入的数据* param - cnt : 要写入的数据长度* param - offt : 相对于文件首地址的偏移* return : 写入的字节数如果为负值表示写入失败*/
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int retvalue;unsigned char databuf[1];unsigned char ledstat;struct gpioled_dev *dev filp-private_data; // 通过读取 filp 的 private_data 成员变量来得到设备结构体变量retvalue copy_from_user(databuf, buf, cnt); /* 接收APP发送过来的数据 */if(retvalue 0) {printk(kernel write failed!\r\n);return -EFAULT;}ledstat databuf[0]; /* 获取状态值 *//* 调用 gpio_set_value 函数来向 GPIO 写入数据实现开/关 LED 的效果。不需要直接操作相应的寄存器 */if(ledstat LEDON) { gpio_set_value(dev-led_gpio, 0); /* 打开LED灯 */} else if(ledstat LEDOFF) {gpio_set_value(dev-led_gpio, 1); /* 关闭LED灯 */}return 0;
}/** description : 关闭/释放设备* param - filp : 要关闭的设备文件(文件描述符)* return : 0 成功;其他 失败*/
static int led_release(struct inode *inode, struct file *filp)
{struct gpioled_dev *dev filp-private_data; // 新加的这里把私有数据给dev/* 关闭驱动文件的时候释放原子变量 */atomic_inc(dev-lock);return 0;
}/* 设备操作函数 */
static struct file_operations gpioled_fops {.owner THIS_MODULE,.open led_open,.read led_read,.write led_write,.release led_release,
};/** description : 驱动出口函数* param : 无* return : 无*/
static int __init led_init(void)
{int ret 0;const char *str;// 1、初始化原子变量gpioled.lock (atomic_t)ATOMIC_INIT(0);// 2、原子变量初始化为atomic_set(gpioled.lock, 1);/* 设置LED所使用的GPIO *//* 1、获取设备节点gpioled */gpioled.nd of_find_node_by_path(/gpioled);if(gpioled.nd NULL) {printk(gpioled node not find!\r\n);return -EINVAL;}/* 2.读取status属性 */ret of_property_read_string(gpioled.nd, status, str); // 获取状态是否是okayif(ret 0) return -EINVAL;if (strcmp(str, okay))return -EINVAL;/* 3、获取compatible属性值并进行匹配 */ret of_property_read_string(gpioled.nd, compatible, str);if(ret 0) {printk(gpioled: Failed to get compatible property\n);return -EINVAL;}if (strcmp(str, alientek,led)) {printk(gpioled: Compatible match failed\n);return -EINVAL;}/* 4、 获取设备树中的gpio属性得到LED所使用的LED编号 */gpioled.led_gpio of_get_named_gpio(gpioled.nd, led-gpio, 0); // 获取 LED 所使用的 LED 编号。相当于将 gpioled 节点中的“led-gpio”属性值转换为对应的 LED 编号if(gpioled.led_gpio 0) {printk(cant get led-gpio);return -EINVAL;}printk(led-gpio num %d\r\n, gpioled.led_gpio);/* 5.向gpio子系统申请使用GPIO */ret gpio_request(gpioled.led_gpio, LED-GPIO); // 这里设备树已经改成了led-gpiogpioi 0 GPIO_ACTIVE_LOWif (ret) {printk(KERN_ERR gpioled: Failed to request led-gpio\n);return ret;}/* 6、设置PI0为输出并且输出高电平默认关闭LED灯 */ret gpio_direction_output(gpioled.led_gpio, 1);if(ret 0) {printk(cant set gpio!\r\n);}/* 注册字符设备驱动 *//* 1、创建设备号 */if (gpioled.major) { /* 定义了设备号 */gpioled.devid MKDEV(gpioled.major, 0);ret register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);if(ret 0) {pr_err(cannot register %s char driver [ret%d]\n, GPIOLED_NAME, GPIOLED_CNT);goto free_gpio;}} else { /* 没有定义设备号 */ret alloc_chrdev_region(gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME); /* 申请设备号 */if(ret 0) {pr_err(%s Couldnt alloc_chrdev_region, ret%d\r\n, GPIOLED_NAME, ret);goto free_gpio;}gpioled.major MAJOR(gpioled.devid); /* 获取分配号的主设备号 */gpioled.minor MINOR(gpioled.devid); /* 获取分配号的次设备号 */}printk(gpioled major%d,minor%d\r\n,gpioled.major, gpioled.minor); /* 2、初始化cdev */gpioled.cdev.owner THIS_MODULE;cdev_init(gpioled.cdev, gpioled_fops);/* 3、添加一个cdev */cdev_add(gpioled.cdev, gpioled.devid, GPIOLED_CNT);if(ret 0)goto del_unregister;/* 4、创建类 */gpioled.class class_create(THIS_MODULE, GPIOLED_NAME);if (IS_ERR(gpioled.class)) {goto del_cdev;}/* 5、创建设备 */gpioled.device device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);if (IS_ERR(gpioled.device)) {goto destroy_class;}return 0;destroy_class:class_destroy(gpioled.class);
del_cdev:cdev_del(gpioled.cdev);
del_unregister:unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
free_gpio:gpio_free(gpioled.led_gpio);return -EIO;
}/** description : 驱动出口函数* param : 无* return : 无*/
static void __exit led_exit(void)
{/* 注销字符设备驱动 */cdev_del(gpioled.cdev);/* 删除cdev */unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */device_destroy(gpioled.class, gpioled.devid);/* 注销设备 */class_destroy(gpioled.class);/* 注销类 */gpio_free(gpioled.led_gpio); /* 释放GPIO */
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE(GPL);
MODULE_AUTHOR(ALIENTEK);
MODULE_INFO(intree, Y); 其次编写 atomicApp.c 测试文件
#include stdio.h
#include unistd.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include stdlib.h
#include string.h#define LEDOFF 0
#define LEDON 1/** description : main主程序* param - argc : argv数组元素个数* param - argv : 具体参数* return : 0 成功;其他 失败*/
int main(int argc, char *argv[])
{int fd, retvalue;char *filename;unsigned char cnt 0;unsigned char databuf[1];if(argc ! 3){printf(Error Usage!\r\n);return -1;}filename argv[1];/* 打开led驱动 */fd open(filename, O_RDWR);if(fd 0){printf(file %s open failed!\r\n, argv[1]);return -1;}databuf[0] atoi(argv[2]); /* 要执行的操作打开或关闭 *//* 向/dev/gpioled文件写入数据 */retvalue write(fd, databuf, sizeof(databuf));if(retvalue 0){printf(LED Control Failed!\r\n);close(fd);return -1;}/* 模拟占用25S LED */while(1) {sleep(5);cnt;printf(App running times:%d\r\n, cnt);if(cnt 5) break;}printf(App running finished!);retvalue close(fd); /* 关闭文件 */if(retvalue 0){printf(file %s close failed!\r\n, argv[1]);return -1;}return 0;
} 1.2 运行测试 编写 Makefile 文件
KERNELDIR : /home/alientek/linux/atk-mpl/linux/my_linux/linux-5.4.31 # Linux内核源码路径
CURRENT_PATH : $(shell pwd)obj-m : atomic.obuild: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M$(CURRENT_PATH) modulesclean:$(MAKE) -C $(KERNELDIR) M$(CURRENT_PATH) clean 编译 Makefile 文件得到 atomic.ko 文件。
make 编译 atomicApp.c 文件得到 atomicApp 文件。
arm-none-linux-gnueabihf-gcc atomicApp.c -o atomicApp 将这两个文件拷贝
sudo cp atomicApp atomic.ko /home/alientek/linux/nfs/rootfs/lib/modules/5.4.31/ 开启开发板输入命令加载 atomic.ko 驱动
depmod
modprobe atomic.ko 输入命令开启 LED并且每隔 5s 都会输出 App running times
./atomicApp /dev/gpioled 1 # “”表示在后台运行 atomicApp 这个软件 当在运行过程中输入以下命令的时候
/atomicApp /dev/gpioled 0 # 关闭 LED 灯 打开 /dev/gpioled 失败原因是 atomicApp 软件正在占用 /dev/gpioled如果再次运行 atomicApp 软件去操作/dev/gpioled 肯定会失败。必须等待 atomicApp运行结束也就是25S结束以后其他软件才能去操作/dev/gpioled。这个就是采用原子变量实现一次只能有一个应用程序访问 LED 灯。 最后卸载驱动
rmmod atomic.ko 二、自旋锁实验 上节是使用原子操作实现一个应用程序访问 LED这次换成自旋锁实现。 首先先注意自旋锁使用事项 ① 自旋锁保护的临界区尽可能的短。使用一个变量来表示设备的使用情况如果设备被使用了那么变量就加一设备被释放以后变量就减 1我们只需要使用自旋锁保护这个变量即可。 ② 考虑驱动兼容性选择合理的 API 函数。 2.1 实验程序编写 不用修改设备树。 把上一节的 atomic.c、Makefiel、atomicApp.c 复制到新的子目录 8_spinlock 中并把 atomic 相关的重命名为 spinlock首先修改 spinlock.c 文件
#include linux/types.h
#include linux/kernel.h
#include linux/delay.h
#include linux/ide.h
#include linux/init.h
#include linux/module.h
#include linux/errno.h
#include linux/gpio.h
#include linux/cdev.h
#include linux/device.h
#include linux/of.h
#include linux/of_address.h
#include linux/of_gpio.h
#include asm/mach/map.h
#include asm/uaccess.h
#include asm/io.h#define GPIOLED_CNT 1 /* 设备号个数 */
#define GPIOLED_NAME gpioled /* 名字 */
#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 *//* gpioled设备结构体 */
struct gpioled_dev{dev_t devid; /* 设备号 */struct cdev cdev; /* cdev */struct class *class; /* 类 */struct device *device; /* 设备 */int major; /* 主设备号 */int minor; /* 次设备号 */struct device_node *nd; /* 设备节点 */int led_gpio; /* led所使用的GPIO编号 */ // 此成员变量保存 LED 等所使用的 GPIO 编号int dev_stats; // 设备使用状态 0设备未使用 0设备使用spinlock_t lock; // 自旋锁
};struct gpioled_dev gpioled; /* led设备 *//** description : 打开设备* param - inode : 传递给驱动的inode* param - filp : 设备文件file结构体有个叫做private_data的成员变量* 一般在open的时候将private_data指向设备结构体。* return : 0 成功;其他 失败*/
static int led_open(struct inode *inode, struct file *filp)
{unsigned long flags; // 中断状态变量filp-private_data gpioled; /* 设置私有数据 */spin_lock_irqsave(gpioled.lock, flags); // 保存中断状态禁止本地中断并获取自旋锁这里为什么没有使用spin_lock就是考虑到兼容性if (gpioled.dev_stats) // 如果设备被使用{spin_unlock_irqrestore(gpioled.lock, flags); // 将中断状态恢复到以前的状态并且激活本地中断释放自旋锁return -EBUSY;}gpioled.dev_stats; // 如果设备没有使用就让stats 0使其使用变为使用中spin_unlock_irqrestore(gpioled.lock, flags); // 解锁return 0;
}/** description : 从设备读取数据 * param - filp : 要打开的设备文件(文件描述符)* param - buf : 返回给用户空间的数据缓冲区* param - cnt : 要读取的数据长度* param - offt : 相对于文件首地址的偏移* return : 读取的字节数如果为负值表示读取失败*/
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{return 0;
}/** description : 向设备写数据 * param - filp : 设备文件表示打开的文件描述符* param - buf : 要写给设备写入的数据* param - cnt : 要写入的数据长度* param - offt : 相对于文件首地址的偏移* return : 写入的字节数如果为负值表示写入失败*/
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int retvalue;unsigned char databuf[1];unsigned char ledstat;struct gpioled_dev *dev filp-private_data; // 通过读取 filp 的 private_data 成员变量来得到设备结构体变量retvalue copy_from_user(databuf, buf, cnt); /* 接收APP发送过来的数据 */if(retvalue 0) {printk(kernel write failed!\r\n);return -EFAULT;}ledstat databuf[0]; /* 获取状态值 *//* 调用 gpio_set_value 函数来向 GPIO 写入数据实现开/关 LED 的效果。不需要直接操作相应的寄存器 */if(ledstat LEDON) { gpio_set_value(dev-led_gpio, 0); /* 打开LED灯 */} else if(ledstat LEDOFF) {gpio_set_value(dev-led_gpio, 1); /* 关闭LED灯 */}return 0;
}/** description : 关闭/释放设备* param - filp : 要关闭的设备文件(文件描述符)* return : 0 成功;其他 失败*/
static int led_release(struct inode *inode, struct file *filp)
{unsigned long flags;struct gpioled_dev *dev filp-private_data; // 新加的这里把私有数据给dev/* 关闭驱动文件的时候将dev_stats减1 */spin_lock_irqsave(dev-lock, flags); // 上锁if (dev-dev_stats) // dev_stats 为成立设备使用中{dev-dev_stats --; // dev_stats 为释放设备}spin_unlock_irqrestore(dev-lock, flags);/* 解锁 */return 0;
}
// 这里有个疑惑为什么oepn和release都要有上锁和解锁是因为确保在同一时间只有一个进程能改变dev_stats的值可以防止竞争的发生/* 设备操作函数 */
static struct file_operations gpioled_fops {.owner THIS_MODULE,.open led_open,.read led_read,.write led_write,.release led_release,
};/** description : 驱动出口函数* param : 无* return : 无*/
static int __init led_init(void)
{int ret 0;const char *str;// 自旋锁初始化spin_lock_init(gpioled.lock);/* 设置LED所使用的GPIO *//* 1、获取设备节点gpioled */gpioled.nd of_find_node_by_path(/gpioled);if(gpioled.nd NULL) {printk(gpioled node not find!\r\n);return -EINVAL;}/* 2.读取status属性 */ret of_property_read_string(gpioled.nd, status, str); // 获取状态是否是okayif(ret 0) return -EINVAL;if (strcmp(str, okay))return -EINVAL;/* 3、获取compatible属性值并进行匹配 */ret of_property_read_string(gpioled.nd, compatible, str);if(ret 0) {printk(gpioled: Failed to get compatible property\n);return -EINVAL;}if (strcmp(str, alientek,led)) {printk(gpioled: Compatible match failed\n);return -EINVAL;}/* 4、 获取设备树中的gpio属性得到LED所使用的LED编号 */gpioled.led_gpio of_get_named_gpio(gpioled.nd, led-gpio, 0); // 获取 LED 所使用的 LED 编号。相当于将 gpioled 节点中的“led-gpio”属性值转换为对应的 LED 编号if(gpioled.led_gpio 0) {printk(cant get led-gpio);return -EINVAL;}printk(led-gpio num %d\r\n, gpioled.led_gpio);/* 5.向gpio子系统申请使用GPIO */ret gpio_request(gpioled.led_gpio, LED-GPIO); // 这里设备树已经改成了led-gpiogpioi 0 GPIO_ACTIVE_LOWif (ret) {printk(KERN_ERR gpioled: Failed to request led-gpio\n);return ret;}/* 6、设置PI0为输出并且输出高电平默认关闭LED灯 */ret gpio_direction_output(gpioled.led_gpio, 1);if(ret 0) {printk(cant set gpio!\r\n);}/* 注册字符设备驱动 *//* 1、创建设备号 */if (gpioled.major) { /* 定义了设备号 */gpioled.devid MKDEV(gpioled.major, 0);ret register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);if(ret 0) {pr_err(cannot register %s char driver [ret%d]\n, GPIOLED_NAME, GPIOLED_CNT);goto free_gpio;}} else { /* 没有定义设备号 */ret alloc_chrdev_region(gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME); /* 申请设备号 */if(ret 0) {pr_err(%s Couldnt alloc_chrdev_region, ret%d\r\n, GPIOLED_NAME, ret);goto free_gpio;}gpioled.major MAJOR(gpioled.devid); /* 获取分配号的主设备号 */gpioled.minor MINOR(gpioled.devid); /* 获取分配号的次设备号 */}printk(gpioled major%d,minor%d\r\n,gpioled.major, gpioled.minor); /* 2、初始化cdev */gpioled.cdev.owner THIS_MODULE;cdev_init(gpioled.cdev, gpioled_fops);/* 3、添加一个cdev */cdev_add(gpioled.cdev, gpioled.devid, GPIOLED_CNT);if(ret 0)goto del_unregister;/* 4、创建类 */gpioled.class class_create(THIS_MODULE, GPIOLED_NAME);if (IS_ERR(gpioled.class)) {goto del_cdev;}/* 5、创建设备 */gpioled.device device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);if (IS_ERR(gpioled.device)) {goto destroy_class;}return 0;destroy_class:class_destroy(gpioled.class);
del_cdev:cdev_del(gpioled.cdev);
del_unregister:unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
free_gpio:gpio_free(gpioled.led_gpio);return -EIO;
}/** description : 驱动出口函数* param : 无* return : 无*/
static void __exit led_exit(void)
{/* 注销字符设备驱动 */cdev_del(gpioled.cdev);/* 删除cdev */unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */device_destroy(gpioled.class, gpioled.devid);/* 注销设备 */class_destroy(gpioled.class);/* 注销类 */gpio_free(gpioled.led_gpio); /* 释放GPIO */
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE(GPL);
MODULE_AUTHOR(ALIENTEK);
MODULE_INFO(intree, Y); 测试 APP 跟上节一样把名字变成 spinlockApp.c 即可。 2.2 运行测试 修改 Makefile 中的 改为 spinlock.o 即可。 编译 spinlock.c 文件
make 编译 spinlockApp.c 文件
arm-none-linux-gnueabihf-gcc spinlockApp.c -o spinlockApp 把以上两个文件复制
sudo cp spinlockApp spinlock.ko /home/alientek/linux/nfs/rootfs/lib/modules/5.4.31/ 开启开发板输入以下命令
cd lib/modules/5.4.31/
depmod
modprobe spinlock.ko 使用 spinlockApp 进行测试驱动
./spinlockApp /dev/gpioled 1 // 打开 LED 灯
./spinlockApp /dev/gpioled 0 // 关闭 LED 灯 驱动正常工作的话不会立马关闭 LED会提示 file /dev/gpioled open failed!必须等待第一个 spinlock App 软件运行完成才可以关闭。 卸载驱动
rmmod spinlock.ko 三、信号量实验 使用信号量来实现只能有一个应用程序访问 LED因为信号量可以导致休眠所以信号量保护的临界区没有运行时间限制就可以在 open 函数申请信号量在 release 函数中释放信号量。 3.1 实验程序编写 不用修改设备树。 新建 9_semaphore 文件夹并按上一小节这样操作只需要把 spinlock 改为 semaphore 即可。修改 semaphore.c 文件
#include linux/types.h
#include linux/kernel.h
#include linux/delay.h
#include linux/ide.h
#include linux/init.h
#include linux/module.h
#include linux/errno.h
#include linux/gpio.h
#include linux/cdev.h
#include linux/device.h
#include linux/of.h
#include linux/of_address.h
#include linux/of_gpio.h
#include asm/mach/map.h
#include asm/uaccess.h
#include asm/io.h#define GPIOLED_CNT 1 /* 设备号个数 */
#define GPIOLED_NAME gpioled /* 名字 */
#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 *//* gpioled设备结构体 */
struct gpioled_dev{dev_t devid; /* 设备号 */struct cdev cdev; /* cdev */struct class *class; /* 类 */struct device *device; /* 设备 */int major; /* 主设备号 */int minor; /* 次设备号 */struct device_node *nd; /* 设备节点 */int led_gpio; /* led所使用的GPIO编号 */ // 此成员变量保存 LED 等所使用的 GPIO 编号struct semaphore sem; // 信号量
};struct gpioled_dev gpioled; /* led设备 *//** description : 打开设备* param - inode : 传递给驱动的inode* param - filp : 设备文件file结构体有个叫做private_data的成员变量* 一般在open的时候将private_data指向设备结构体。* return : 0 成功;其他 失败*/
static int led_open(struct inode *inode, struct file *filp)
{filp-private_data gpioled; /* 设置私有数据 */if (down_interruptible(gpioled.sem)); // 获取信号量,进入休眠以后是可以被信号打断的,这时候count为0{return -ERESTARTSYS;}/* down(gpioled.sem); // 获取信号量不能被信号打断
*/return 0;
}/** description : 从设备读取数据 * param - filp : 要打开的设备文件(文件描述符)* param - buf : 返回给用户空间的数据缓冲区* param - cnt : 要读取的数据长度* param - offt : 相对于文件首地址的偏移* return : 读取的字节数如果为负值表示读取失败*/
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{return 0;
}/** description : 向设备写数据 * param - filp : 设备文件表示打开的文件描述符* param - buf : 要写给设备写入的数据* param - cnt : 要写入的数据长度* param - offt : 相对于文件首地址的偏移* return : 写入的字节数如果为负值表示写入失败*/
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int retvalue;unsigned char databuf[1];unsigned char ledstat;struct gpioled_dev *dev filp-private_data; // 通过读取 filp 的 private_data 成员变量来得到设备结构体变量retvalue copy_from_user(databuf, buf, cnt); /* 接收APP发送过来的数据 */if(retvalue 0) {printk(kernel write failed!\r\n);return -EFAULT;}ledstat databuf[0]; /* 获取状态值 *//* 调用 gpio_set_value 函数来向 GPIO 写入数据实现开/关 LED 的效果。不需要直接操作相应的寄存器 */if(ledstat LEDON) { gpio_set_value(dev-led_gpio, 0); /* 打开LED灯 */} else if(ledstat LEDOFF) {gpio_set_value(dev-led_gpio, 1); /* 关闭LED灯 */}return 0;
}/** description : 关闭/释放设备* param - filp : 要关闭的设备文件(文件描述符)* return : 0 成功;其他 失败*/
static int led_release(struct inode *inode, struct file *filp)
{struct gpioled_dev *dev filp-private_data; // 这里把私有数据给devup(dev-sem); // 释放信号量信号量count值加1return 0;
}/* 设备操作函数 */
static struct file_operations gpioled_fops {.owner THIS_MODULE,.open led_open,.read led_read,.write led_write,.release led_release,
};/** description : 驱动出口函数* param : 无* return : 无*/
static int __init led_init(void)
{int ret 0;const char *str;/* 初始化信号量 */sema_init(gpioled.sem, 1);/* 设置LED所使用的GPIO *//* 1、获取设备节点gpioled */gpioled.nd of_find_node_by_path(/gpioled);if(gpioled.nd NULL) {printk(gpioled node not find!\r\n);return -EINVAL;}/* 2.读取status属性 */ret of_property_read_string(gpioled.nd, status, str); // 获取状态是否是okayif(ret 0) return -EINVAL;if (strcmp(str, okay))return -EINVAL;/* 3、获取compatible属性值并进行匹配 */ret of_property_read_string(gpioled.nd, compatible, str);if(ret 0) {printk(gpioled: Failed to get compatible property\n);return -EINVAL;}if (strcmp(str, alientek,led)) {printk(gpioled: Compatible match failed\n);return -EINVAL;}/* 4、 获取设备树中的gpio属性得到LED所使用的LED编号 */gpioled.led_gpio of_get_named_gpio(gpioled.nd, led-gpio, 0); // 获取 LED 所使用的 LED 编号。相当于将 gpioled 节点中的“led-gpio”属性值转换为对应的 LED 编号if(gpioled.led_gpio 0) {printk(cant get led-gpio);return -EINVAL;}printk(led-gpio num %d\r\n, gpioled.led_gpio);/* 5.向gpio子系统申请使用GPIO */ret gpio_request(gpioled.led_gpio, LED-GPIO); // 这里设备树已经改成了led-gpiogpioi 0 GPIO_ACTIVE_LOWif (ret) {printk(KERN_ERR gpioled: Failed to request led-gpio\n);return ret;}/* 6、设置PI0为输出并且输出高电平默认关闭LED灯 */ret gpio_direction_output(gpioled.led_gpio, 1);if(ret 0) {printk(cant set gpio!\r\n);}/* 注册字符设备驱动 *//* 1、创建设备号 */if (gpioled.major) { /* 定义了设备号 */gpioled.devid MKDEV(gpioled.major, 0);ret register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);if(ret 0) {pr_err(cannot register %s char driver [ret%d]\n, GPIOLED_NAME, GPIOLED_CNT);goto free_gpio;}} else { /* 没有定义设备号 */ret alloc_chrdev_region(gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME); /* 申请设备号 */if(ret 0) {pr_err(%s Couldnt alloc_chrdev_region, ret%d\r\n, GPIOLED_NAME, ret);goto free_gpio;}gpioled.major MAJOR(gpioled.devid); /* 获取分配号的主设备号 */gpioled.minor MINOR(gpioled.devid); /* 获取分配号的次设备号 */}printk(gpioled major%d,minor%d\r\n,gpioled.major, gpioled.minor); /* 2、初始化cdev */gpioled.cdev.owner THIS_MODULE;cdev_init(gpioled.cdev, gpioled_fops);/* 3、添加一个cdev */cdev_add(gpioled.cdev, gpioled.devid, GPIOLED_CNT);if(ret 0)goto del_unregister;/* 4、创建类 */gpioled.class class_create(THIS_MODULE, GPIOLED_NAME);if (IS_ERR(gpioled.class)) {goto del_cdev;}/* 5、创建设备 */gpioled.device device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);if (IS_ERR(gpioled.device)) {goto destroy_class;}return 0;destroy_class:class_destroy(gpioled.class);
del_cdev:cdev_del(gpioled.cdev);
del_unregister:unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
free_gpio:gpio_free(gpioled.led_gpio);return -EIO;
}/** description : 驱动出口函数* param : 无* return : 无*/
static void __exit led_exit(void)
{/* 注销字符设备驱动 */cdev_del(gpioled.cdev);/* 删除cdev */unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */device_destroy(gpioled.class, gpioled.devid);/* 注销设备 */class_destroy(gpioled.class);/* 注销类 */gpio_free(gpioled.led_gpio); /* 释放GPIO */
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE(GPL);
MODULE_AUTHOR(ALIENTEK);
MODULE_INFO(intree, Y); 当信号量 sem 为 1 的时候表示 LED 灯还没有被使用如果应用程序 A 要使用LED 灯先调用 open 函数打开/dev/gpioled这个时候会获取信号量 sem获取成功以后 sem 的值减 1 变为 0。如果此时应用程序 B 也要使用 LED 灯调用 open 函数打开/dev/gpioled 就会因为信号量无效(值为 0)而进入休眠状态。当应用程序 A 运行完毕调用 close 函数关闭/dev/gpioled的时候就会释放信号量 sem此时信号量 sem 的值就会加 1变为 1。信号量 sem 再次有效表示其他应用程序可以使用 LED 灯了此时在休眠状态的应用程序 B 就会获取到信号量 sem获取成功以后就开始使用 LED 灯。 3.2 运行测试 修改 Makefile 文件跟上节一样只不过改为 obj-m : semaphore.o。 编译 semaphore.c 文件
make 编译 semaphoreApp.c 文件
arm-none-linux-gnueabihf-gcc semaphoreApp.c -o semaphoreApp 最后将以上两个文件复制
sudo cp semaphoreApp semaphore.ko /home/alientek/linux/nfs/rootfs/lib/modules/5.4.31/ 开启开发板加载驱动
depmod
modprobe semaphore.ko 驱动加载完成使用 semaphoreApp 测试驱动
./semaphoreApp /dev/gpioled 1 # 打开 LED 灯
./semaphoreApp /dev/gpioled 0 # 关闭 LED 灯 首先第一条命令先获取信号量因此可以操作 LED所以这时候开发板上面的 LED 是亮着的。第二条命令因为也想获得 LED 使用权但是被第一条命令抢先了所以第二条命令就休眠等到第一条命令完成的时候释放信号量第二条命令才能拥有 LED 使用权这时候发现开发板的 LED 是灭的。总共开发板前 25s 亮后 25s 灭。 卸载驱动
rmmod semaphore.ko 四、互斥体实验 其实最适合互斥的就是互斥体 mutex。怎么感觉在说废话。 4.1 实验程序编写 不用修改设备树。 跟上节一样的操作全部改为 mutex。对了每次记得添加头文件路径修改 mutex.c 文件
#include linux/types.h
#include linux/kernel.h
#include linux/delay.h
#include linux/ide.h
#include linux/init.h
#include linux/module.h
#include linux/errno.h
#include linux/gpio.h
#include linux/cdev.h
#include linux/device.h
#include linux/of.h
#include linux/of_address.h
#include linux/of_gpio.h
#include asm/mach/map.h
#include asm/uaccess.h
#include asm/io.h#define GPIOLED_CNT 1 /* 设备号个数 */
#define GPIOLED_NAME gpioled /* 名字 */
#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 *//* gpioled设备结构体 */
struct gpioled_dev{dev_t devid; /* 设备号 */struct cdev cdev; /* cdev */struct class *class; /* 类 */struct device *device; /* 设备 */int major; /* 主设备号 */int minor; /* 次设备号 */struct device_node *nd; /* 设备节点 */int led_gpio; /* led所使用的GPIO编号 */ // 此成员变量保存 LED 等所使用的 GPIO 编号struct mutex lock; // 定义互斥体
};struct gpioled_dev gpioled; /* led设备 *//** description : 打开设备* param - inode : 传递给驱动的inode* param - filp : 设备文件file结构体有个叫做private_data的成员变量* 一般在open的时候将private_data指向设备结构体。* return : 0 成功;其他 失败*/
static int led_open(struct inode *inode, struct file *filp)
{filp-private_data gpioled; /* 设置私有数据 */if (mutex_lock_interruptible(gpioled.lock)) // 获取互斥体可以被信号打断{return -ERESTARTSYS;}/* mutex_lock(gpioled.lock); // 获取信号量不能被信号打断
*/return 0;
}/** description : 从设备读取数据 * param - filp : 要打开的设备文件(文件描述符)* param - buf : 返回给用户空间的数据缓冲区* param - cnt : 要读取的数据长度* param - offt : 相对于文件首地址的偏移* return : 读取的字节数如果为负值表示读取失败*/
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{return 0;
}/** description : 向设备写数据 * param - filp : 设备文件表示打开的文件描述符* param - buf : 要写给设备写入的数据* param - cnt : 要写入的数据长度* param - offt : 相对于文件首地址的偏移* return : 写入的字节数如果为负值表示写入失败*/
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int retvalue;unsigned char databuf[1];unsigned char ledstat;struct gpioled_dev *dev filp-private_data; // 通过读取 filp 的 private_data 成员变量来得到设备结构体变量retvalue copy_from_user(databuf, buf, cnt); /* 接收APP发送过来的数据 */if(retvalue 0) {printk(kernel write failed!\r\n);return -EFAULT;}ledstat databuf[0]; /* 获取状态值 *//* 调用 gpio_set_value 函数来向 GPIO 写入数据实现开/关 LED 的效果。不需要直接操作相应的寄存器 */if(ledstat LEDON) { gpio_set_value(dev-led_gpio, 0); /* 打开LED灯 */} else if(ledstat LEDOFF) {gpio_set_value(dev-led_gpio, 1); /* 关闭LED灯 */}return 0;
}/** description : 关闭/释放设备* param - filp : 要关闭的设备文件(文件描述符)* return : 0 成功;其他 失败*/
static int led_release(struct inode *inode, struct file *filp)
{struct gpioled_dev *dev filp-private_data; // 这里把私有数据给devmutex_unlock(dev-lock); // 释放互斥锁return 0;
}/* 设备操作函数 */
static struct file_operations gpioled_fops {.owner THIS_MODULE,.open led_open,.read led_read,.write led_write,.release led_release,
};/** description : 驱动出口函数* param : 无* return : 无*/
static int __init led_init(void)
{int ret 0;const char *str;/* 初始化互斥体 */mutex_init(gpioled.lock);/* 设置LED所使用的GPIO *//* 1、获取设备节点gpioled */gpioled.nd of_find_node_by_path(/gpioled);if(gpioled.nd NULL) {printk(gpioled node not find!\r\n);return -EINVAL;}/* 2.读取status属性 */ret of_property_read_string(gpioled.nd, status, str); // 获取状态是否是okayif(ret 0) return -EINVAL;if (strcmp(str, okay))return -EINVAL;/* 3、获取compatible属性值并进行匹配 */ret of_property_read_string(gpioled.nd, compatible, str);if(ret 0) {printk(gpioled: Failed to get compatible property\n);return -EINVAL;}if (strcmp(str, alientek,led)) {printk(gpioled: Compatible match failed\n);return -EINVAL;}/* 4、 获取设备树中的gpio属性得到LED所使用的LED编号 */gpioled.led_gpio of_get_named_gpio(gpioled.nd, led-gpio, 0); // 获取 LED 所使用的 LED 编号。相当于将 gpioled 节点中的“led-gpio”属性值转换为对应的 LED 编号if(gpioled.led_gpio 0) {printk(cant get led-gpio);return -EINVAL;}printk(led-gpio num %d\r\n, gpioled.led_gpio);/* 5.向gpio子系统申请使用GPIO */ret gpio_request(gpioled.led_gpio, LED-GPIO); // 这里设备树已经改成了led-gpiogpioi 0 GPIO_ACTIVE_LOWif (ret) {printk(KERN_ERR gpioled: Failed to request led-gpio\n);return ret;}/* 6、设置PI0为输出并且输出高电平默认关闭LED灯 */ret gpio_direction_output(gpioled.led_gpio, 1);if(ret 0) {printk(cant set gpio!\r\n);}/* 注册字符设备驱动 *//* 1、创建设备号 */if (gpioled.major) { /* 定义了设备号 */gpioled.devid MKDEV(gpioled.major, 0);ret register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);if(ret 0) {pr_err(cannot register %s char driver [ret%d]\n, GPIOLED_NAME, GPIOLED_CNT);goto free_gpio;}} else { /* 没有定义设备号 */ret alloc_chrdev_region(gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME); /* 申请设备号 */if(ret 0) {pr_err(%s Couldnt alloc_chrdev_region, ret%d\r\n, GPIOLED_NAME, ret);goto free_gpio;}gpioled.major MAJOR(gpioled.devid); /* 获取分配号的主设备号 */gpioled.minor MINOR(gpioled.devid); /* 获取分配号的次设备号 */}printk(gpioled major%d,minor%d\r\n,gpioled.major, gpioled.minor); /* 2、初始化cdev */gpioled.cdev.owner THIS_MODULE;cdev_init(gpioled.cdev, gpioled_fops);/* 3、添加一个cdev */cdev_add(gpioled.cdev, gpioled.devid, GPIOLED_CNT);if(ret 0)goto del_unregister;/* 4、创建类 */gpioled.class class_create(THIS_MODULE, GPIOLED_NAME);if (IS_ERR(gpioled.class)) {goto del_cdev;}/* 5、创建设备 */gpioled.device device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);if (IS_ERR(gpioled.device)) {goto destroy_class;}return 0;destroy_class:class_destroy(gpioled.class);
del_cdev:cdev_del(gpioled.cdev);
del_unregister:unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
free_gpio:gpio_free(gpioled.led_gpio);return -EIO;
}/** description : 驱动出口函数* param : 无* return : 无*/
static void __exit led_exit(void)
{/* 注销字符设备驱动 */cdev_del(gpioled.cdev);/* 删除cdev */unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */device_destroy(gpioled.class, gpioled.devid);/* 注销设备 */class_destroy(gpioled.class);/* 注销类 */gpio_free(gpioled.led_gpio); /* 释放GPIO */
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE(GPL);
MODULE_AUTHOR(ALIENTEK);
MODULE_INFO(intree, Y); 4.2 运行测试 修改 Makefile mutex.o。 编译 mutex.c 文件和 mutexApp.c 文件
make
arm-none-linux-gnueabihf-gcc mutexApp.c -o mutexApp 上面两个文件复制到
sudo cp mutexApp mutex.ko /home/alientek/linux/nfs/rootfs/lib/modules/5.4.31/ 加载驱动
depmod
modprobe mutex.ko 使用 mutexApp 测试驱动
./mutexApp /dev/gpioled 1 # 打开 LED 灯
./mutexApp /dev/gpioled 0 # 关闭 LED 灯 跟信号量的效果一样。卸载驱动
rmmod mutex.ko