首页
关于
Search
1
开发环境搭建(FTP、VSCode、交叉编译器、SSH、NFS、TFTP)
33 阅读
2
linux内核编译报错(gcc: not found + fatal error curses.h)
26 阅读
3
安装WMware Tools选项显示灰色解决方法及WMware Tools安装笔记
17 阅读
4
ubuntu24.2下解压.tar.bz2报错(bzip2: 无法exec:没有那个文件或目录)
14 阅读
5
linux内核编译报错(usr/bin/ld: scripts/dtc/dtc-parser.tab.o:(.bss+0x50): multiple definition of 'yylloc';)
14 阅读
学习笔记
linux学习笔记
文件系统
FreeRTOS
资源分享
小作品
登录
/
注册
Search
Warren
累计撰写
37
篇文章
累计收到
1
条评论
首页
栏目
学习笔记
linux学习笔记
文件系统
FreeRTOS
资源分享
小作品
页面
关于
搜索到
37
篇与
的结果
2024-08-04
Linux设备树(1)
什么是设备树?个人理解 设备树就是一个采用数形结构描述板级设备信息的文件。比如在开发板上搞好了一个工程。但是产品的硬件与开发板有引脚差异,则只需要修改设备树文件即可。其他地方就可以不用动了。DTS、DTB、DTC关系DTS:设备树源码文件DTB:DTS编译后的二进制文件DTC:将DTC编译为DTB的工具设备数文件存放在Linux源代码/arch/arm/boot/dts文件夹编译dts文件,需要进入Linux源码根目录下,执行命令//全部编译(包括zImage、.ko驱动模块、设备树) make all //只编译设备树 make dtbsDTS语法头文件和C语言一样,设备树也支持头文件。头文件一般分为三种,.h文件、.dtsi文件、.dts文件。它们都通过 #include 来引用。一般.dtsi 文件用于描述 SOC 的内部外设信息,比如 CPU 架构、主频、外设寄存器地址范围,比如 UART、 IIC 等等设备节点板子上的每一个设备都是一个节点,叫做设备节点每一个节点都通过一些属性信息来描述节点信息属性信息就是键-值对例如第一行的 / 指的是根节点。一个设备数文件只允许有一个根节点,两个设备树文件都有根节点且一个包含一个,则根节点会合并。第2行、第6行、第17行,是节点的3个子节点,在设备树中节点命令格式如下//node_name:节点名字 //unit-address:设备的地址或寄存器首地址(如果某个节点没有地址或者寄存器的话“unit-address”可以不要) node_name@unit-address例如第6行的cpus和第17行的interrupt-controller@00a01000在第10行有cpu0: cpu@0的描述,是因为引入了 节点标签 。目的为了方便访问节点。可以直接通过&label 来访问这个节点,而不是node-name@unit-address。格式如下label: node-name@unit-address节点搞完了,然后就是节点里面的属性了。每个节点都有不同属性,不同的属性又有不同的内容,属性都是键值对。值可以为空或任意的字节流,常用的数据形式如下//1.字符串 device_type = "cpu"; //2.32位无符号整数 或 32位无符号整数组 reg = <0> reg = <0x00a01000 0x1000> //3.字符串列表 compatible = "arm,cortex-a7";标准属性1.compatible属性compatible 属性也叫做“兼容性”属性。compatible 属性的值是一个字符串列表,用于将设备和驱动绑定起来。compatible 属性的值格式如下所示://manufacturer:厂商 //model:驱动名字 "manufacturer,model" eg: compatible = "fsl,imx6ul-evk-wm8960它的作用就是让驱动来匹配,是否能使用该节点中的参数。比如compatible = "fsl,imx6ul-evk-wm8960","fsl,imx-audio-wm8960";就代表这个节点中的参数适用与飞思卡尔的imx6ul-evk-wm8960驱动和飞思卡尔的imx-audio-wm8960驱动。那么有个问题,驱动怎么知道自己应该使用那个参数?答案是驱动代码在编写时会有一个OF匹配表,如果设备节点的 compatible 属性值和 OF 匹配表中的任何一个值相等,那么就表示设备可以使用这个驱动。OF匹配表如下所示:static const struct of_device_id imx_wm8960_dt_ids[] = { { .compatible = "fsl,imx-audio-wm8960", }, { /* sentinel */ } };2.model属性model 属性值也是一个字符串,一般 model 属性描述设备模块信息,用于指定设备的制造商和型号,推荐使用“制造商, 型号”的格式。从软件的层面讲model属性仅仅表示一个名字而已,没有更多的作用。eg: model = "Alientek i.MX6ULL Board";3.status属性status 属性是设备的状态信息,其属性值也是字符串,可选的状态如下('okay'(运行中)、'disabled'(待启用)、'fail'(严重故障)和'fail-sss'(特定错误))4.reg属性reg 属性的值一般是(address, length)对,用于描述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息。结构如下:reg = <address1 length1 address2 length2 address3 length3……> eg: reg = <0x60000000, 0x100, 0x00002000, 0x80>一般来说,地址长度可能不准确,我们一般使用的都是起始地址信息。5.#address-cells 和#size-cells 属性这两个属性可以用在任何 拥有子节点的设备中 ,用于描述 子节点的地址信息 。属性#address-cells告诉你我们的地址是32为还是64位。 #address-cells = ;就是说你的地址是32位,#address-cells = ;就是说你的地址是64位。属性#size-cells属性:表示地址大小范围。和#address-cells一样。#size-cells值一般为1,还没见过为2的情况。例如: 对于32位的地址#address-cells = <1>: reg = <0x02000000 0x10000>这种情况我们很常见了就是 0x02000000~0x02000000+0x10000地址空间 对于64位的地址#address-cells = <2>: reg = <0x10000000 0x02000000 0x10000>这种情况我们64位的地址就是高32位是0x10000000,低32位是0x02000000的64位地址 所以他代表的地址空间就是:0x1000000002000000 + 0x10000的地址空间6.ranges 属性ranges属性值可以为空或者按照(child-bus-address,parent-bus-address,length)格式编写的数字矩阵。child-bus-address:子总线地址空间的物理地址,由父节点的#address-cells 确定此物理地址所占位宽。parent-bus-address: 父总线地址空间的物理地址,同样由父节点的#address-cells 确定此物理地址所占位宽。length: 子地址空间的长度,由父节点的#size-cells 确定此地址长度所占位宽。例如:#address-cells = <1>; #size-cells = <1>; ranges = <0x0 0xe0000000 0x00100000>; reg = <0x4600 0x100>;ranges属性值指定了一个 1024KB(0x00100000)的地址范围,子地址空间的物理起始地址为 0x0,父地址空间的物理起始地址为 0xe0000000。reg 属性定义了 serial 设备寄存器的起始地址为 0x4600,寄存器长度为 0x100。经过地址转换, serial 设备可以从 0xe0004600 开始进行读写操作,0xe0004600=0x4600+0xe0000000。7.name属性name 属性值为字符串, name 属性用于记录节点名字, name 属性已经被弃用,不推荐使用name 属性,一些老的设备树文件可能会使用此属性。(个人认为和label一样)。8.device_type属性device_type 属性值为字符串,用于描述设备的 FCode,但是设备树没有 FCode,所以此属性也被抛弃了。此属性只能用于 cpu 节点或者 memory 节点。在cpu节点中有device_type = "cpu";根节点compatible属性根节点的compatible属性一般有两个值,第一个值描述了所使用的硬件设备名字(eg:imx6ull-14x14-evk)第二个值描述了设备所使用的 SOC(eg:imx6ull)它的作用就让Linux内核查看是否支持此设备,如果支持的话设备就会启动Linux内核。匹配方式分为两种(老版本 和 新版本)1.老版本uboot 会给 Linux 内核传递 machine id 这个参数,Linux 内核会检查这个 machineid,其实就是将 machine id 与 MACH_TYPE_XXX 宏进行对比,看看有没有相等的,如果相等的话就表示 Linux 内核支持这个设备,如果不支持的话那么这个设备就没法启动 Linux 内核。个人理解: uboot传输了一个特定的字符串给Linux,Linux通过宏将该字符串转换为Linux内部特定的宏,再将该宏与.h中的支持列表一一对比。2.新版本machine_desc 结构体中有个.dt_compat 成员变量,此成员变量保存着本设备兼容属性。只要某个设备(板子)根节点“ /”的 compatible 属性值与xxx_dt_compat 表中的任何一个值相等,那么就表示 Linux 内核支持此设备个人理解:uboot将设备树根节点的compatible 属性值传输给Linux。同时Linux内有个兼容表,将传输过来的属性值与兼容表进行一一对比。向节点追加内容 或 修改节点内容其实向节点追加或修改节点内容的操作是一样的。必须我们要向设备中增加一个IIC接口的MPU6050外设,我们找到IIC节点在imx6ull.dtsi文件中,但是我们不能在imx6ull.dtsi文件中修改。因为imx6ull.dtsi是一个头文件,这个头文件被很多个板子设备树文件所调用。如果在它里面修改了。那么所有使用imx6ull这个片子的设备都会被加一个MPU6050外设。正确的做法就是:打开alientek这个板子专属的设备树文件(.dts),找到要添加设备的父节点的标号(比如i2c1)。在专属的设备树文件追加下列内容:&i2c1{ /*要追加或修改的内容*/ /*节点不存在 = 新增节点*/ /*节点存在,内容不存在 = 新增内容*/ /*节点存在,内容存在 = 修改内容*/ };
2024年08月04日
4 阅读
0 评论
0 点赞
2024-08-02
depmod命令报错(depmod: WARNING: could not open /lib/modules/4.1.15/modules.order: No such file or directory..)
错误现象报错原因分析也就是说在这个文件夹中丢失了两个文件 modules.order 和 modules.builtin解决方法从Linux源码下复制这两个文件到该文件夹下测试问题解决
2024年08月02日
3 阅读
0 评论
0 点赞
2024-07-30
新字符设备驱动
设备号的分配和获取在之前的实验中,我们使用register_chrdev函数来向内核注册一个字符设备,使用unregister_chrdev释放注册的设备号。他们的使用参数都需要我们指定一个设备号。但在使用过程中这样的操作存在两个问题。我们如何事先知道那些设备号没有被使用?register_chrdev只需要传入主设备号,次设备号没有被使用,造成浪费? 为此采用新的函数来处理设备号的使用,对申请而言,我们分为两种情况。第一种是我们不指定设备号,第二种是我们指定设备号。如果没有指定设备号的话就使用如下函数来申请设备号//dev_t *dev 存放设备号的地址 //unsigned baseminor 起始次设备号 //unsigned count 要申请的设备号数量(一次申请多个,主设备号一样,次设备号不同) //const char *name 设备名 int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)如果给定了设备的主设备号和次设备号就使用如下所示函数来注册设备号即可//dev_t from 要申请的起始设备号 //unsigned count 要申请的设备号数量 //const char *name 设备名 int register_chrdev_region(dev_t from, unsigned count, const char *name)释 放 掉 设 备 号 , 不 管 是 通 过 alloc_chrdev_region 函 数 还 是register_chrdev_region 函数申请的设备号,统一使用如下释放函数//dev_t from 要释放的设备号 //unsigned count 表示从 from 开始,要释放的设备号数量 void unregister_chrdev_region(dev_t from, unsigned count)dev_t 设备号dev_t结构体实际上就是一个uint32_t的变量。其中高12bit代表主设备号,低20bit代表次设备号。在文件include/linux/kdev_t.h中关于设备号的操作函数如下所示:#define MINORBITS XX #define MINORMASK ((1U << MINORBITS) - 1) #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))字符设备结构在 Linux 中使用 cdev 结构体表示一个字符设备, cdev 结构体在 include/linux/cdev.h 文件中//kobj: //*owner:填写THIS_MODULE //*ops:文件操作函数集合 //list: //dev:设备号 //count: struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; struct list_head list; dev_t dev; unsigned int count; }定义好cdev变量后通过cdev_init 函数对其进行初始化。//*cdev:字符设备变量地址 //*fops:字符设备对应操作函数集合地址 void cdev_init(struct cdev *cdev, const struct file_operations *fops)初始化完cdev变量需要调用cdev_add函数向Linux系统添加字符设备//*p: cdev结构体变量地址 //dev: 设备所使用的设备号 //count: 要添加的设备数量 int cdev_add(struct cdev *p, dev_t dev, unsigned count)卸载驱动时候要使用cdev_del函数,这样才能从Linux内核中删除对应的字符设备//*p: cdev结构体变量地址 void cdev_del(struct cdev *p)自动创建删除设备节点为了实现自动创建设备节点及自动删除设备节点。需要引入mdev机制,mdev 来实现设备节点文件的自动创建与删除,热插拔事件也由 mdev 管理。 自动创建设备节点的工作是在驱动程序的入口函数中完成的,一般在 cdev_add 函数后面添加自动创建设备节点相关代码( 类的创建和设备的创建 )//*owner 填写THIS_MODULE //char *name 类名称 //return 创建好的类 struct class *class_create (struct module *owner, const char *name) //*class 创建的类 //*parent 一般为NULL //devt 设备号 //drvdata 一般为NULL //*fmt 设备名 //return 创建好的设备 struct device *device_create(struct class *class,struct device *parent,dev_t devt,void *drvdata,const char *fmt, ...)同样的在卸载驱动时候需要删除掉创建的类和设备//*cls 要删除的类的地址 void class_destroy(struct class *cls); //*class 要删除的设备所处的类 //devt 要删除的设备号 void device_destroy(struct class *class, dev_t devt)设置文件私有数据就是将这一系列的变量封成一个结构体,然后再open函数中设置filp的私有数据指向该结构体变量。 设置好私有数据以后,在 write、 read、 close 等函数中直接读取 private_data 即可得到设备结构体例子:struct test_dev{ dev_t devid; /* 设备号 */ struct cdev cdev; /* cdev */ struct class *class; /* 类 */ struct device *device; /* 设备 */ int major; /* 主设备号 */ int minor; /* 次设备号 */ }; static int test_open(struct inode *inode, struct file *filp) { filp->private_data = &testdev; /* 设置私有数据 */ return 0; }驱动代码实现#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 <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h> #define NEWCHRLED_CNT 1 #define NEWCHRLED_NAME "newchrled" #define LEDOFF 0 #define LEDON 1 #define CCM_CCGR1_BASE (0x020C406C) #define SW_MUX_GPIO1_IO03_BASE (0x020E0068) #define SW_PAD_GPIO1_IO03_BASE (0x020E02F4) #define GPIO1_DR_BASE (0x0209C000) #define GPIO1_GDIR_BASE (0x0209C004) static void __iomem *CCM_CCGR1; static void __iomem *SW_MUX_GPIO1_IO03; static void __iomem *SW_PAD_GPIO1_IO03; static void __iomem *GPIO1_DR; static void __iomem *GPIO1_GDIR; struct newchrled_dev{ dev_t devid; struct cdev cdev; struct class *class; struct device *device; int major; int minor; }; struct newchrled_dev newchrled; void led_switch(u8 sta) { u32 val = 0; if(sta == LEDOFF){ val = readl(GPIO1_DR); val |= (1 << 3); writel(val, GPIO1_DR); }else if(sta == LEDON){ val = readl(GPIO1_DR); val &= ~(1 << 3); writel(val, GPIO1_DR); } } void led_device_init(void) { u32 val = 0; CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4); SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4); SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4); GPIO1_DR = ioremap(GPIO1_DR_BASE, 4); GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4); /*使能GPIO1时钟*/ val = readl(CCM_CCGR1); val &= ~(3 << 26); val |= (3 << 26); writel(val, CCM_CCGR1); /*设置GPIO1——IO3的复用功能*/ writel(5, SW_MUX_GPIO1_IO03); /*设置SW_PAD_GPIO1_IO03的IO属性*/ writel(0x10B0, SW_PAD_GPIO1_IO03); /*设置GPIO1_IO03为输出功能*/ val = readl(GPIO1_GDIR); val &= ~(1 << 3); val |= (1 << 3); writel(val, GPIO1_GDIR); /*关闭LED*/ led_switch(LEDOFF); } u32 led_status_get(void) { u32 val = 0; val = readl(GPIO1_DR); val = (val >> 3)&0x01; return (!val); } void led_device_deinit(void) { led_switch(LEDOFF); iounmap(CCM_CCGR1); iounmap(SW_MUX_GPIO1_IO03); iounmap(SW_PAD_GPIO1_IO03); iounmap(GPIO1_DR); iounmap(GPIO1_GDIR); } static int led_open(struct inode *inode, struct file *filp) { filp->private_data = &newchrled; return 0; }; static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { u32 led_status = 0; led_status = led_status_get(); copy_to_user(buf, &led_status, sizeof(u32)); return 0; } static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) { u32 led_ctrl = 0; copy_from_user(&led_ctrl, buf, sizeof(u32)); led_switch(led_ctrl & 0x01); return 0; } static int led_release(struct inode *inode, struct file *filp) { return 0; } static struct file_operations newchrled_fops = { .owner = THIS_MODULE, .open = led_open, .read = led_read, .write = led_write, .release = led_release, }; static int __init led_init(void) { led_device_init(); if(newchrled.major){ newchrled.devid = MKDEV(newchrled.major, 0); register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME); }else{ alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME); newchrled.major = MAJOR(newchrled.devid); newchrled.minor = MINOR(newchrled.devid); } printk("newchrled major=%d minor=%d\n", newchrled.major, newchrled.minor); /*初始化cdev*/ newchrled.cdev.owner = THIS_MODULE; cdev_init(&newchrled.cdev, &newchrled_fops); /*添加一个cdev*/ cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT); /*创建类*/ newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME); if(IS_ERR(newchrled.class)){ return PTR_ERR(newchrled.class); } /*创建设备*/ newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME); if(IS_ERR(newchrled.device)){ return PTR_ERR(newchrled.device); } return 0; } static void __exit led_exit(void) { led_device_deinit(); cdev_del(&newchrled.cdev); unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT); device_destroy(newchrled.class, newchrled.devid); class_destroy(newchrled.class); } module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("warren");测试代码实现#include "stdio.h" #include "unistd.h" #include "sys/types.h" #include "fcntl.h" #include "stdlib.h" #include "string.h" int main(int argc, int argv[]) { int retvalue = 0; int Dir = 0; char *filename; int fd = 0; unsigned int buff; if(argc < 4){ printf("Error Usage!\r\n"); return -1; } filename = argv[1]; fd = open(filename, O_RDWR); if(fd < 0){ printf("Open %s error!\r\n",filename); return -1; } Dir = atoi(argv[2]); if(Dir == 1){//写入 buff = atoi(argv[3]); retvalue = write(fd, &buff, sizeof(unsigned int)); }else if(Dir == 2){//读取 retvalue = read(fd, &buff, sizeof(unsigned int)); printf("Led status is %d\r\n", buff); } return 0; }脚本文件编译脚本#!/bin/bash make clean make rm -rf ledApp arm-linux-gnueabihf-gcc ledApp.c -o ledApp rm -rf ../../tftpboot/* cp *App *.ko ../../tftpboot chmod 777 ../../tftpboot/*tftp获取脚本#!/bin/bash rm -rf led* tftp -g -r newchrled.ko 192.168.0.100 tftp -g -r ledApp 192.168.0.100 chmod 777 ledApp测试及实验1.关闭开发板的心跳灯echo none>/sys/class/leds/sys-led/trigger2.获取驱动模块文件和测试应用程序文件./get.sh3.加载newchrled.ko驱动模块depmod modprobe newchrled在实验depmod命令时,会报错。但是不影响正常使用,下个帖子将怎么解决可以看到申请到的主设备号为249,次设备号为04.查看设备节点ls /dev/newchrled -l5.使用测试应用来测试./ledApp /dev/newchrled 1 1 ./ledApp /dev/newchrled 2 1 ./ledApp /dev/newchrled 1 0 ./ledApp /dev/newchrled 2 06.卸载驱动模块rmmod newchrled
2024年07月30日
4 阅读
0 评论
0 点赞
2024-07-23
嵌入式Linux LED驱动开发
地址映射(MMU)MMU全称Memory Manage Unit,也就是内存管理单元主要功能:完成虚拟空间到物理空间的映射虚拟地址范围为0~2^32=4GB需要用到两个函数实现物理内存与虚拟内存之间的转换 ioremap//ioremap用于获取指定物理地址空间对应的虚拟地址空间 iounmap//iounmao用于取消映射的虚拟地址空间函数原型如下 #define ioremap(cookie,size) __arm_ioremap((cookie), (size),MT_DEVICE) void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size,unsigned int mtype) ioremap是个宏,正真起作用的是__arm_ioremap。__arm_ioremap有3个参数:phys_addr size mtype。 phys_addr:要映射给的物理起始地址 size:要映射的内存空间大小 mtype:ioremap 的类型(ioremap 函数选择 MT_DEVICE) void iounmap (volatile void __iomem *addr) iounmap 只有一个参数 addr,此参数就是要取消映射的虚拟地址空间首地址使用方法:注意__arm_ioremap函数的返回值,返回值是一个void __iomem类型的指针。指向映射后的虚拟空间首地址1.定义一个指针,并使用static修饰static void __iomem ADDRESS2.调用ioremap实现地址映射ADDRESS = ioremap(0x00000000,4);3.调用iounmap取消地址映射iounmap(ADDRESS);I/O内存访问函数这里说的 I/O 不是我们学习单片机的时候讲的 GPIO 引脚。这里涉及到两个概念(I/O 端口和 I/O 内存)I/O 端口: 当外部寄存器或内存映射到 IO空间 时,称为 I/O 端口I/O 内存: 当外部寄存器或内存映射到 内存空间 时,称为 I/O 内存。对于ARM来说没有IO空间这个概念。只有IO内存,使用 ioremap 函数将寄存器的物理地址映射到虚拟地址以后,我们就可以直接通过指针访问这些地址。但是 但是 但是!!! Linux 内核不建议这么做,而是推荐使用一组操作函数来对映射后的内存进行读写操作。操作函数如下u8 readb(const volatile void __iomem *addr) u16 readw(const volatile void __iomem *addr) u32 readl(const volatile void __iomem *addr) void writeb(u8 value, volatile void __iomem *addr) void writew(u16 value, volatile void __iomem *addr) void writel(u32 value, volatile void __iomem *addr)驱动代码实现#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 <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h> #define LED_MAJOR 200 #define LED_NAME "led" #define LED_OFF 0 #define LED_ON 1 #define CCM_CCGR1_BASE (0x020C406C) #define SW_MUX_GPIO1_IO03_BASE (0x020E0068) #define SW_PAD_GPIO1_IO03_BASE (0x020E02F4) #define GPIO1_DR_BASE (0x0209C000) #define GPIO1_GDIR_BASE (0x0209C004) static void __iomem *CCM_CCGR1; static void __iomem *SW_MUX_GPIO1_IO03; static void __iomem *SW_PAD_GPIO1_IO03; static void __iomem *GPIO1_DR; static void __iomem *GPIO1_GDIR; void led_switch(u8 sta) { u32 val = 0; if(sta == LED_ON){ val = readl(GPIO1_DR); val &= ~(1 << 3); writel(val, GPIO1_DR); }else if (sta == LED_OFF) { val = readl(GPIO1_DR); val |= (1 << 3); writel(val, GPIO1_DR); } } void led_device_init(void) { u32 val = 0; /*寄存器地址映射*/ CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4); SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4); SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4); GPIO1_DR = ioremap(GPIO1_DR_BASE, 4); GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4); /*使能GPIO1时钟*/ val = readl(CCM_CCGR1); val &= ~(3<<26); val |= (1<<26); writel(val, CCM_CCGR1); /*设置GPIO1_IO3的复用功能*/ writel(5, SW_MUX_GPIO1_IO03); /*设置IO属性*/ writel(0x10B0, SW_PAD_GPIO1_IO03); /*设置GPIO1_IO3为输出功能*/ val = readl(GPIO1_GDIR); val &= ~(1 << 3); val |= (1 << 3); writel(val, GPIO1_GDIR); /*LED默认关闭*/ led_switch(LED_OFF); } void led_device_deinit(void) { iounmap(CCM_CCGR1); iounmap(SW_MUX_GPIO1_IO03); iounmap(SW_PAD_GPIO1_IO03); iounmap(GPIO1_DR); iounmap(GPIO1_GDIR); } static int led_open(struct inode *inode, struct file *filp) { return 0; } static int led_release(struct inode *inode, struct file *filp) { return 0; } static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { u32 val = 0; u32 led_status = 0; int retvalue = 0; val = readl(GPIO1_DR); led_status = !(val&(1<<3)); retvalue = copy_to_user(buf, &led_status, sizeof(u32)); return retvalue; } static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) { u32 led_value = 0; int retvalue = 0; retvalue = copy_from_user(&led_value, buf, sizeof(u32)); led_switch(led_value&0x01); return retvalue; } struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, .release = led_release, .read = led_read, .write = led_write, }; static int __init led_init(void) { int retvalue = 0; led_device_init(); retvalue = register_chrdev(LED_MAJOR, LED_NAME, &led_fops); printk("led init success\r\n"); return retvalue; } static void __exit led_exit(void) { led_device_deinit(); unregister_chrdev(LED_MAJOR, LED_NAME); printk("led deinit success\r\n"); } module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("warren");测试代码实现#include "stdio.h" #include "unistd.h" #include "sys/types.h" #include "fcntl.h" #include "stdlib.h" #include "string.h" int main(int argc, int argv[]) { int retvalue = 0; int Dir = 0; char *filename; int fd = 0; unsigned int buff; if(argc < 4){ printf("Error Usage!\r\n"); return -1; } filename = argv[1]; fd = open(filename, O_RDWR); if(fd < 0){ printf("Open %s error!\r\n",filename); return -1; } Dir = atoi(argv[2]); if(Dir == 1){//写入 buff = atoi(argv[3]); retvalue = write(fd, &buff, sizeof(unsigned int)); }else if(Dir == 2){//读取 retvalue = read(fd, &buff, sizeof(unsigned int)); printf("Led status is %d\r\n", buff); } return 0; }脚本文件编译脚本#!/bin/bash make clean make rm -rf ledApp arm-linux-gnueabihf-gcc ledApp.c -o ledApp rm -rf ../../tftpboot/* cp *App *.ko ../../tftpboot chmod 777 ../../tftpboot/*tftp获取脚本#!/bin/bash rm -rf led* tftp -g -r led.ko 192.168.0.100 tftp -g -r ledApp 192.168.0.100 chmod 777 ledApp测试及实验1.编译驱动模块及测试程序2.在开发板上获取驱动模块及测试程序3.加载驱动模块 insmod led.ko4.创建设备节点 insmod led.ko4.开打LED灯并获取状态 ./ledApp /dev/led 1 1 ./ledApp /dev/led 2 x5.关闭LED灯并获取状态 ./ledApp /dev/led 1 0 ./ledApp /dev/led 2 xOVER ::(捂嘴笑) {lamp/}
2024年07月23日
4 阅读
0 评论
1 点赞
2024-07-16
字符设备驱动开发
驱动模块的加载与卸载 insmod xxx.ko //加载驱动模块 rmmod xxx.ko //卸载驱动模块 module_init(xxx_init);//注册模块加载函数 module_exit(xxx_exit);//注册模块卸载函数使用insmod命令加载驱动模块,在调用insmod命令时候,驱动中的mudule_init();函数就会被调用。使用rmmod命令卸载驱动模块,在调用rmmod命令时,驱动中的module_exit();函数就会被调用。问题1:linux系统如何知道mudule_init()和module_exit()是驱动的入口函数和出口函数?答案:mudule_init()需要用"__init"来修饰,module_exit()需要用"__exit"来修饰。问题2:驱动的入口函数和出口函数为何要用ststic修饰?答案:static修饰后的函数为静态函数,它不能被其他文件所用,起隔离作用。所以字符驱动设备模块加载和卸载函数的模板如下/*驱动入口*/ static int __init xxxx_init(void) { /*填入具体内容*/ return 0; } /*驱动出口*/ static void __exit xxxx_exit(void) { /*填入具体内容*/ } /*指定模块的入口函数和出口函数*/ module_init(xxxx_init); module_exit(xxxx_exit);字符设备的注册与注销当调用insmod命令加载设备驱动时,module_init会调用执行xxxx_init;那么怎么能让系统知道我们加载了一个什么驱动。则就需要进行驱动的注册。同理,我们卸载驱动时,也需要进行驱动的注销函数原型如下: static inline init register_chrdev(unsigned int major, const char *name, const struct file_operations *fops); static inline void unregister_chrdev(unsigned int major, const char *name);各个参数的意义如下:major:主设备号name:设备名称fops:结构体 file_operation类型指针(后面再详细写)字符设备的注册与注销模板如下static struct file_operations xxx_fops static int __init xxx_init(void) { int retvalue = 0; retvalue = register_chrdev(200, "xxx", &xxx_fops); if(retvalue < 0){ return -1; } return 0; } static void __exit xxx_exit(void) { unregister_chrdev(200, "xxx"); } module_init(xxx_init); module_exit(xxx_exit);file_operation结构体介绍struct file_operations { struct module *owner; ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); int (*open) (struct inode *, struct file *); int (*release) (struct inode *, struct file *); ......//省略其他 };owner拥有该结构体的模块的指针,一般设置为THIS_MODULEwrite函数用于向设备文件写入(发送数据)read函数用于读取设备文件open函数用于打开设备文件release函数用于释放(关闭)设备文件其他的后面再做笔记 在linux中一切皆文件,驱动加载成功后,会在/dev目录下生成一个相应的文件,应用程序通过访问这个名为/dev/xxx的文件实现对驱动的控制。一般对文件的操作有open、close、write、read。 以open函数为例,我们在应用程序中调用open函数,这个函数是C库提供给应用使用的,C库则会通过系统调用陷入到内核中调用驱动的open函数 所以我们要在驱动中实现file_operations中的write、read、open、release函数。通过注册设备函数提供给系统,供应用使用。设备具体操作函数具体实现/*打开设备*/ // inode:传递给驱动的inode // filp:设备文件 static int xxx_open(struct inode *inode, struct file *filp) { /*用户实现具体功能*/ return 0; }/*关闭设备*/ // inode:传递给驱动的inode // filp:设备文件 static int xxx_release(struct inode *inode, struct file *filp) { /*用户实现具体功能*/ return 0; }/*从设备读取*/ // filp:要打开的设备文件(文件描述符) // buf:返回给用户空间的数据缓冲区 // cnt:要读取的数据长度 // offt:相对于文件首地址的偏移 static ssize_t xxx_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { /*用户实现具体功能*/ return 0; }/*从设备读取*/ // file:设备文件 // buf:要写入设备的数据 // cnt:写入的数据长度 // offt:相对于文件首地址的偏移 static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) { /*用户实现具体功能*/ return 0; }添加LICENSE和作者信息MODULE_LICENSE() //添加模块 LICENSE 信息 MODULE_AUTHOR() //添加模块作者信息许可信息填写GPL,作者信息填写自己的即可, LICENSE 是必须添加的,否则的话编译的时候会报错,作者信息可以添加也可以不添加。file_operations结构体变量的实现我们虽然定义的file_operations xxx_fops。并且xxx_fops可以通过注册设备函数传递给系统,但是xxx_fops是个空的,需要我们初始化它。结构如下。static struct file_operations xxx_fops = { .owner = THIS_MODULE, .open = xxx_open, .read = xxx_read, .write = xxx_write, .release = xxx_release, }; 其中.owner固定赋值为THIS_MODULE{lamp/}至此驱动部门完成了,具体实现代码如下。 #include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/init.h> #include <linux/module.h> #define CHRDEVBASE_MAJOR 200 #define CHRDEVBASE_NAME "chrdevbase" static char readbuf[1024]; static char writebuf[1024]; static char kerneldata[1024]={0}; static int chrdevbase_open(struct inode *inode, struct file *filp) { printk("chrdevbase open!\r\n"); return 0; } static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { int retvalue = 0; if(cnt <= 1024){ memcpy(readbuf, kerneldata, cnt); retvalue = copy_to_user(buf, readbuf, cnt); }else{ memcpy(readbuf, kerneldata, 1024); retvalue = copy_to_user(buf, readbuf, 1024); } if(retvalue == 0){ printk("kernel senddata success!\r\n"); }else{ printk("kernel senddata failed!\r\n"); } return 0; } static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) { int retvalue = 0; if(cnt <= 1024){ retvalue = copy_from_user(writebuf, buf, cnt); }else{ retvalue = copy_from_user(writebuf, buf, 1024); } memcpy(kerneldata, writebuf, strlen(writebuf)); if(retvalue == 0){ printk("kernel receive data success!\r\n"); }else{ printk("kernel receive data failed!\r\n"); } return 0; } static int chrdevbase_release(struct inode *inode, struct file *filp) { // printk("chrdevbase release!\r\n"); return 0; } static struct file_operations chrdevbase_fops = { .owner = THIS_MODULE, .open = chrdevbase_open, .release = chrdevbase_release, .read = chrdevbase_read, .write = chrdevbase_write }; static int __init chrdevbase_init(void) { int retvalue = 0; retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops); if(retvalue == 0){ printk("chrdevbase init success\r\n"); }else{ printk("chrdevbase init failed\r\n"); } return 0; } static void __exit chrdevbase_exit(void) { unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME); printk("chrdevbase exit success!\r\n"); } module_init(chrdevbase_init); module_exit(chrdevbase_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("warren");测试软件代码如下#include "stdio.h" #include "unistd.h" #include "sys/types.h" #include "sys/stat.h" #include "fcntl.h" #include "stdlib.h" #include "string.h" static char usrdata[]={"usr data!"}; int main(int argc, int **argv) { int fd, retvalue; char *filename; char readbuf[1024]; char writebuf[1024]; if(atoi(argv[2]) == 1){ if(argc != 3){ printf("Error Usage!\r\n"); return -1; } }else{ if(argc != 4){ printf("Error Usage!\r\n"); return -1; } } filename = argv[1]; fd = open(filename, O_RDWR); if(fd < 0){ printf("Can't open %s\r\n", filename); } if(atoi(argv[2]) == 1){ retvalue = read(fd, readbuf, 1024); if(retvalue != 0){ printf("read file %s failed\r\n", filename); return -1; }else{ printf("read data: %s\r\n", readbuf); } } else if (atoi(argv[2]) == 2){ memcpy(writebuf, argv[3], strlen(argv[3])); retvalue = write(fd, writebuf, strlen(argv[3])); if(retvalue != 0){ printf("write file %s failed!\r\n", filename); }else{ printf("write data %s success!\r\n", argv[3]); } } retvalue = close(fd); if(retvalue != 0){ printf("Can't Close file %s\r\n", filename); return -1; } return 0; }驱动程序和应用程序编译//编译驱动模块的MAKEFILE文件 KERNELDIR := /home/warren/linux/linux_source_code/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek CURRENTDIR := $(shell pwd) obj-m := chrdevbase.o build: kernel_modules kernel_modules: $(MAKE) -C $(KERNELDIR) M=$(CURRENTDIR) modules clean: $(MAKE) -C $(KERNELDIR) M=$(CURRENTDIR) cleanMAKEFILE知识后面再补吧,先暂时当一下烂娃。编译驱动模块和应用的脚本及拷贝到tftp文件夹,准备让开发板获取。#!/bin/bash make clean make rm -rf chrdevbaseApp arm-linux-gnueabihf-gcc chrdevbaseApp.c -o chrdevbaseApp rm -rf ../../tftpboot/* cp c*App *.ko ../../tftpboot chmod 777 ../../tftpboot/*测试及验证进入在开发板的根文件系统/lib/modules/4.1.15文件夹使用tftp命令获取chrdevbase.ko文件和chrdevbaseApp文件tftp -g -r chrdevbase.ko 192.168.0.100 tftp -g -r chrdevbaseApp 192.168.0.100给chrdevbaseApp权限chmod 777 chrdevbaseApp使用insmod命令加载驱动模块insmod chrdevbase.ko查看当前系统中的所有设备ccat /proc/devices创建设备节点文件mknod /dev/chrdevbase c 200 0 // mknod:创建节点命令 // /dev/chrdevbase:要创建的节点文件 // c:代表字符 // 200: 主设备号 // 0:次设备号查看/dev/下的节点文件下来调用chrdevbaseApp来对/dev/chedevbase这个节点文件进行操作。./chrdevbaseApp /dev/chrdevbase 2 warren./chrdevbaseApp /dev/chrdevbase 1{lamp/}结束,撒花!!! ::(开心)
2024年07月16日
7 阅读
0 评论
0 点赞
1
...
5
6
7
8