驱动知识点
Linux驱动之字符设备驱动框架与示例模板
https://blog.csdn.net/Wang_XB_3434/article/details/131739858
设备号
https://blog.csdn.net/qq_53221728/article/details/129464604
Linux设备主要分为字符设备、块设备、网络设备
字符设备:能够像字节流一样被访问且没有缓冲是按顺序访问的设备,当对字符设备发出读写请求,相应的IO操作立即发生。Linux系统中很多设备都是字符设备,绝大多数的设备都是字符设备。比如LED、按键、键盘、串口、传感器、LCD。字符设备驱动通过字符设备文件来访问。
块设备:按照数据块来访问且有缓冲是具有随机访问能力的设备,比如访问硬盘就不是一个字节一个字节的访问,而是直接访问数据块,数据块的大小是固定的但是不同的系统不一样。比如内存、磁盘、SD卡、U盘。块设备驱动通过块设备文件来访问。
网络设备:网络设备由Linux的网络子系统驱动,负责数据包的发送和接收,而不是面向流设备,因此在Linux系统文件系统中网络设备没有节点。访问网络设备不通过文件,通过套接字(网络通信地址)访问。
在Linux内核源码中,使用结构体dev_t类型来定义设备号。实际上dev_t类型为32位的unsigned int类型(在Linux内核源码中可以进行跟踪)。其中高12位作为存储主设备号,低20位作为存储次设备号。
可以在/proc/devices文件中查询哪些主设备号以及被使用了。
内核中提供了操作设备号的宏
MAJOR(设备号);//通过设备号获取主设备号
MINOR(设备号);//通过设备号获取次设备号
MKDEV(主设备号,次设备号);//通过主设备号和次设备号构造设备号
实际上在Linux内核中提供了两种方法可以进行分配主设备号。分别为静态申请设备号和动态分配设备号。
静态申请
-
选择一个内核中未被使用的主设备号(在/proc/devices中查看)。
-
根据设备个数分配次设备号,一般从0开始。
-
使用宏构造完整的设备号。
-
调用函数register_chrdev_region向系统申请。
-
不再使用设备号需要注销,通过函数unregister_chrdev_region实现注销。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#define CDD_MAJOR 220 //主设备号
#define CDD_MINOR 0 //起始次设备号
#define CDD_COUNT 1 //设备号个数
dev_t dev; //设备号
int cdd_init(void) //加载函数
{
int ret;
//构造设备号
dev = MKDEV(CDD_MAJOR, CDD_MINOR);
// 静态申请设备号
ret = register_chrdev_region(dev, CDD_COUNT, "cdd_demo");
if(ret<0){
printk("register_chrdev_region failed!\n");
return ret;
}
printk("register_chrdev_region success!\n");
return 0;
}
void cdd_exit(void) //卸载函数
{
unregister_chrdev_region(dev, CDD_COUNT); //注销设备号
}
//声明为模块的入口和出口
module_init(cdd_init);
module_exit(cdd_exit);
MODULE_LICENSE("GPL");
动态申请
就是向系统申请一个设备号,系统自动分配一个没有使用过的设备号。
使用函数alloc_chrdev_region向系统申请。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#define CDD_MINOR 0 //起始次设备号
#define CDD_COUNT 1 //设备号个数
dev_t dev; //设备号
int cdd_init(void) //加载函数
{
int ret;
// 动态态申请设备号
ret = alloc_chrdev_region(&dev, CDD_MINOR, CDD_COUNT, "cdd_demo");
if(ret<0){
printk("register_chrdev_region failed!\n");
return ret;
}
printk("register_chrdev_region success!\n");
printk("major number:%d\n",MAJOR(dev));
return 0;
}
void cdd_exit(void) //卸载函数
{
unregister_chrdev_region(dev, CDD_COUNT); //注销设备号
}
//声明为模块的入口和出口
module_init(cdd_init);
module_exit(cdd_exit);
MODULE_LICENSE("GPL");
设备节点
https://blog.csdn.net/qq_41483419/article/details/129250200
手动创建
mknod /dev/test c 242 0
自动创建
创建类,在类下创建设备。
驱动点灯
https://whycan.com/p_72912.html
寄存器分析:使用Allwinner_F1C600_User_Manual_V1.0.pdf
这里使用E2引脚,则n=4,板子使用LicheePi Nano
需要配置的寄存器
板卡没有LED灯,这里调试飞线连接1个发光二极管和电阻。
实现一、register_chrdev 函数注册字符设备
lec.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
#define DEV_MAJOR 200 /* 主设备号 */
#define DEV_NAME "led" /* 设备名 */
#define PE_CFG0_REG 0x01C20800 + 4 * 0x24 //PE配置寄存器 A:0 B:1 C:2 ....
#define PE_DATA_REG 0x01C20800 + 4 * 0x24 + 0x10 //PE数据寄存器 A:0 B:1 C:2 ....
#define PIN_N 2 // 第2个引脚
#define N (PIN_N % 8 * 4) //引脚x : x % 8 * 4
//volatile unsigned int *gpio_con = NULL;
//volatile unsigned int *gpio_dat = NULL;
static void __iomem *gpio_con = NULL;
static void __iomem *gpio_dat = NULL;
/*打开设备*/
static int led_open (struct inode *node, struct file *filp)
{
if (gpio_con) { /*打开成功*/
return 0;
} else {
return -1;
}
}
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
return 0;
}
/*写设备*/
static ssize_t led_write (struct file *filp, const char __user *buf, size_t size, loff_t *off)
{
int ret;
unsigned char val;
unsigned int temp_val;
ret = copy_from_user(&val, buf, 1);
if(ret < 0) /* 如果 不成功复制则返回尚未成功复制剩下的字节数, 这里 就不做 纠错 机制了 */
{
printk(KERN_ERR "copy_from_user fail \n");
return -EFAULT;
}
temp_val = readl(gpio_dat);
if (val)
{
//*gpio_dat |= (1 << PIN_N);//引脚0设置1
temp_val |= (1 << PIN_N);//引脚0设置1
}
else
{
//*gpio_dat &= ~(1 << PIN_N);//引脚0设置0
temp_val &= ~(1 << PIN_N);//引脚0设置0
}
writel(temp_val, gpio_dat);
return 0;
}
/*关闭设备*/
static int led_release (struct inode *node, struct file *filp)
{
return 0;
}
/*
* 设备操作函数结构体
*/
static struct file_operations myled_oprs = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
/* 设备初始化 */
static int myled_init(void)
{
int ret;
unsigned int val;
ret = register_chrdev(DEV_MAJOR, DEV_NAME, &myled_oprs);
if (ret < 0) {
printk(KERN_ERR "register chrdev failed\n");
return -EIO;
}
//gpio_con = (volatile unsigned int *)ioremap(PE_CFG0_REG, 1);
//gpio_dat = (volatile unsigned int *)ioremap(PE_DATA_REG, 1);
//gpio_dat = gpio_con + 4; //数据寄存器 指针+4是移动了4*4=16个字节 原来是0x24 现在是0x34
//*gpio_con &= ~(7 << N); //7=111 取反000 0:2设置000 默认是0x7=111 失能
//*gpio_con |= (1 << N); //设置输出 0:2设置001
//*gpio_dat &= ~(1 << PIN_N); //第0个引脚初始化设置0
gpio_con = ioremap(PE_CFG0_REG, 4);
gpio_dat = ioremap(PE_DATA_REG, 4);
val = readl(gpio_con);
val &= ~(7 << N);
val |= (1 << N);
writel(val, gpio_con);
val = readl(gpio_dat);
val &= ~(1 << PIN_N);
writel(val, gpio_dat);
printk("init!\r\n");
return 0;
}
static void __exit myled_exit(void)
{
/* 注销字符设备驱动 */
iounmap(gpio_con);
iounmap(gpio_dat);
unregister_chrdev(DEV_MAJOR, DEV_NAME);
printk("exit!\r\n");
}
//注册模块加载函数
module_init(myled_init);
//卸载模块加载函数
module_exit(myled_exit);
//开源信息
MODULE_LICENSE("GPL");
//作者
MODULE_AUTHOR("wangxinchen");
Makefile
ifneq ($(KERNELRELEASE),)
#kbuild syntax. dependency relationshsip of files and target modules are listed here.
obj-m := led.o
else
PWD:= $(shell pwd)
KDIR := /home/wang/workplace/code/linux-5.2
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
rm -rf .*.cmd *.o *.mod.c .tmp_versions *.mod *.symvers *.order
clean:
rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions *.mod *.symvers *.order
endif
led_test.c
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
/* ledtest /dev/led_1 on
* ledtest /dev/led_1 off
*
*/
int main(int argc, char **argv)
{
int fd;
int a;
unsigned char val = 1;
if (argc != 3)
{
printf("Usage :\n");
printf(&#