linux驱动开发篇
1.在工厂编译核源码
1.1 复制linux内核源码到Ubuntu中
创建新目录
mkdir -p IMX6/linux-imx-4.1.15-2.1.0
解压linux内核源码
tar xf linux-imx-4.1.15-2.1.0-gb78e551-v1.4.tar.xz -C IMX6/linux-imx-4.1.15-2.1.0/
进入解压目录
cd IMX6/linux-imx-4.1.15-2.1.0
检查解压后的文件 执行编译
./build.sh
完成编译和查看 tmp 目录下的编译目标文件如下图所示,包含许多 dtb 和 Linux 内核 zImage,还有 modules.tar.bz(内核模块)。
2、NFS环境搭建
Windows 主机 IP:192.168.0.104 Ubuntu 虚拟机 IP:192.168.0.108
开发板 IP:192.168.0.106
执行以下指令设置开发板 IP,创建一个 get 虚拟机(192.168.0.108)NFS 将共享目录挂载到开发板上 get 目录中。
mkdir get mount -t nfs -o nolock,nfsvers=3 192.168.0.105:/home/alientek/linux/nfs get
查看挂载的 NFS 目录:
df
完成环境建设后,虚拟机辅助生成的可执行程序nfs下面是挂载目录。
运行QT程序
下次重启不能加载时,重启nfs。重启以下指令 NFS 服务器。
sudo /etc/init.d/nfs-kernel-server restart
查看以下指令 NFS 共享目录。
showmount -e
2022.5.17 P1029字符驱动开发。 驱动加载:insmod drv.ko 驱动卸载:rmmod drv.ko
2022.5.18 视频第一节
驱动是获取外设,获取传感器数据,控制外设。将数据交给应用程序。 通过系统调用/软中断进入内核,操作内核。
字符设备
1.驱动设备的性能是/dev/下面的文件。dev/led。调用应用程序open例如,函数打开设备led。通过应用程序write函数向/dev/led写入数据,如1打开,0关闭。如果关闭设备,则为close函数。
2.编写驱动器时,主要是编写驱动器对应open、close、write函数。由字符设备驱动fileopation_struct结构成员。
3.我的第一个Linux驱动实验。 参考本文件下的字符驱动编写。 编译linux驱动时需要使用linux内核源码,因此要解压缩。编译完成之后得到zImage的.dtb设备树。需要编译zImage和.dtb。 修改路径: ,MINOR(dev_t)。也可以通过MKDEV(major,minor)获取设备号。
注册字符设备,当用了一个主设备号,剩下把次设备号全部都使用了。
查看设备号。
file_operation
结构体: ### 第一个linux设备驱动
/** *my first driver * */
#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 CHARDEVBASE_MAJOR 200 //主设备号
#define CHARDEVBASE_NAME "chrdevbase" //驱动名称
static int chrdev_base_open(struct inode *inode, struct file *filp)
{
printk("chrdev_base_open\n");
return 0;
}
static int chrdev_base_relase(struct inode *inode, struct file *filp)
{
printk("chrdev_base_relase\n");
return 0;
}
static ssize_t chrdev_base_read(struct file *filp, __user char *buf, size_t count,loff_t *ppos)
{
printk("chrdev_base_read\n");
return 0;
}
static ssize_t chrdev_base_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos)
{
printk("chrdev_base_write\n");
return 0;
}
/** * 字符设备的操作集合 */
static struct file_operations chrdev_base_fops = {
.owner = THIS_MODULE,
.open = chrdev_base_open,
.release = chrdev_base_relase,
.read = chrdev_base_read,
.write = chrdev_base_write,
};
static int __init chardevbase_init(void)
{
int ret = 0;
printk("权海洋,加油!\n");
ret = register_chrdev(CHARDEVBASE_MAJOR,CHARDEVBASE_NAME,&chrdev_base_fops);
if(ret < 0){
printk("chrdevbase init failed\n");
}
return 0;
}
static void __exit chardevbase_exit(void)
{
unregister_chrdev(CHARDEVBASE_MAJOR,CHARDEVBASE_NAME);
printk("权海洋,再见!\n");
}
//模块加载函数
module_init(chardevbase_init);
//模块卸载
module_exit(chardevbase_exit);
MODULE_LICENSE("QHY");
加载设备驱动之后,在进入/dev之后查看设备文件,并没有看到设备文件,原因是我们没有创建设备节点。 创建设备节点。 创建设备节点成功。 应用程序
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
int fd = 0;
int ret = 0;
char readbuf[10];
char writebuf[100];
fd = open(argv[1], O_RDWR);
if (fd<0)
{
perror("open error");
exit(-1);
}
/*read*/
ret = read(fd, readbuf,10);
if (ret < 0)
{
perror("read error");
}
/*write*/
ret = write(fd, writebuf,50);
if (ret < 0)
{
perror("write error");
}else{
}
/*close*/
close(fd);
exit(0);
}
通过应用层程序操作驱动程序成功。 chardevbase虚拟设备的驱动。 要求:应用程序对驱动程序进行读写操作,读的话就是从驱动里面读取字符串,写的话就是向驱动写字符串。
应用程序不能访问内核数据!必须借助其他函数。 驱动程序:
/** *my first driver * */
#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/init.h>
#define CHARDEVBASE_MAJOR 200 //主设备号
#define CHARDEVBASE_NAME "chrdevbase" //驱动名称
static char readbuf[100]; /*读缓冲*/
static char writebuf[100]; /*读缓冲*/
static char kernelbuf[100] = {
"kernel data"};
static int chrdev_base_open(struct inode *inode, struct file *filp)
{
//printk("chrdev_base_open\n");
return 0;
}
static int chrdev_base_relase(struct inode *inode, struct file *filp)
{
//printk("chrdev_base_relase\n");
return 0;
}
static ssize_t chrdev_base_read(struct file *filp, __user char *buf, size_t count,loff_t *ppos)
{
//printk("chrdev_base_read\n");
int ret = 0;
memcpy(readbuf,kernelbuf,sizeof(kernelbuf));
ret = copy_to_user(buf,readbuf,count);
if (ret < 0){
}else{
}
return 0;
}
static ssize_t chrdev_base_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos)
{
//printk("chrdev_base_write\n");
int ret = 0;
ret = copy_from_user(writebuf,buf,count);
if (ret<0){
printk("chrdev_base_write error");
}else{
printk("kernel receive:%s\r\n",writebuf);
}
return 0;
}
/** * 字符设备的操作集合 */
static struct file_operations chrdev_base_fops = {
.owner = THIS_MODULE,
.open = chrdev_base_open,
.release = chrdev_base_relase,
.read = chrdev_base_read,
.write = chrdev_base_write,
};
static int __init chardevbase_init(void)
{
int ret = 0;
ret = register_chrdev(CHARDEVBASE_MAJOR,CHARDEVBASE_NAME,&chrdev_base_fops);
if(ret < 0){
printk("chrdevbase init failed\n");
}
return 0;
}
static void __exit chardevbase_exit(void)
{
unregister_chrdev(CHARDEVBASE_MAJOR,CHARDEVBASE_NAME);
}
//模块加载函数
module_init(chardevbase_init);
//模块卸载
module_exit(chardevbase_exit);
MODULE_LICENSE("QHY");
测试应用程序:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
/** * 1 表示向驱动读取数据 * * 2 表示向驱动写入数据 * */
int main(int argc, char **argv)
{
if (argc != 3)
{
printf("Error Usage:\r\n");
return -1;
}
int fd = 0;
int ret = 0;
char readbuf[10];
char writebuf[100];
static char usrdata[100] = {
"qhy data!"};
fd = open(argv[1], O_RDWR);
if (fd<0)
{
perror("open error");
exit(-1);
}
if (atoi(argv[2]) == 1){
/*read*/
ret = read(fd, readbuf,10);
if (ret < 0)
{
perror("read error");
}else{
printf("read data: %s\r\n", readbuf);
}
}else{
}
if (atoi(argv[2]) == 2){
memcpy(writebuf,usrdata,sizeof(usrdata));
/*write*/
ret = write(fd, writebuf,50);
if (ret < 0)
{
perror("write error");
}else{
}
}
/*close*/
close(fd);
exit(0);
}
实验现象
2022 .5.19 完成P7
2022.5.20 P8学习之旅
裸机LED灯实验就是操作+6ULL的寄存器,Linux也可以操作寄存器。了解Linux下如何操作寄存器,Linux下不能直接对寄存器的物理地址进行读写。比如寄存器A的物理地址为0x01010101
。裸机可以直接对这个物理地址进行操作,但是在linux下不行,因为linux会使能MMU
。 所以,在linux 下操作的都是虚拟地址,需要先得到物理地址转换成虚拟地址。 地址映射函数原型:
第一个参数物理地址的起始地址,第二个参数是要转换的字节数量。
va = ioremap(0x01010101,10);
卸载驱动的时候,需要释放掉地址映射。 卸载驱动的时候:iounmap(va);
P9
手撕代码。
实践操作: 内存地址映射:
/*首先定义寄存器的物理地址*/
#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 *IMX6U_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;
入口初始化:
IMX6U_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);
通过寄存器操作对GPIO的初始化操作:
/*2、初始化*/
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3<<26);/*清零bit26 27*/
val |= (3<<26); /*bit 26 27置1*/
writel(val,IMX6U_CCM_CCGR1);
writel(0x5,SW_MUX_GPIO1_IO03);//设置复用
writel(0x10b0,SW_PAD_GPIO1_IO03);//设置电气属性
val = readl(GPIO1_GDIR);
val |= 1<<3;/*bit3 置为1 设置为输出*/
writel(val,GPIO1_GDIR);
val = readl(GPIO1_DR);
val &= ~(1<<3);/*bit3 清0 打开led*/
writel(val,GPIO1_DR);
如果使用的是心跳灯,必须关闭这个心跳灯,否则干扰实验现象。
echo none > /sys/class/leds/sys-led/trigger // 改变 LED 的触发模式
加载写完的驱动。 实验现象:
通过应用程序操作led
编译内核和应用程序。 加载驱动,发现字符设备已经存在了。 创建设备节点,并且查看设备节点。 应用程序操作led灯。 本节的C应用代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
/** * 1 表示向驱动读取数据 * * 2 表示向驱动写入数据 * */
/** * ./ledApp <filename> < 0 1 > 0表示关灯 1表示开灯 * ./ledApp /dev/led 1 * ./ledApp /dev/led 0 */
#define LEDOFF 0
#define LEDON 1
int main(int argc, char **argv)
{
int fd = 0;
int ret = 0;
unsigned char databuf[1];
if (argc != 3)
{
printf("Error Usage:\r\n");
return -1;
}
fd = open(argv[1], O_RDWR);
if (fd<0)
{
perror("open error");
exit(-1);
}
databuf[0] = atoi(argv[2]);
ret = write(fd, databuf,sizeof(databuf));
if (ret < 0)
{
perror("write error");
close(fd);
exit(-1);
}else{
}
/*close*/
close(fd);
exit(0);
}
本节的驱动代码:
/** *my first driver * */
#include <linux/types.h>
#include <linux/kernel.h>
#inc