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.bz2(内核模块)。
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挂载的目录下面。
下次开启重启无法加载的时候,重启nfs。执行以下指令重启 NFS 服务器。
sudo /etc/init.d/nfs-kernel-server restart
执行以下指令查看 NFS 共享目录。
showmount -e
2022.5.17 P1029字符驱动开发。
驱动加载:insmod drv.ko
驱动卸载:rmmod drv.ko
驱动就是获取外设,获取传感器数据,控制外设。数据交给应用程序。
通过系统调用/软中断陷入内核,操作内核。
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。
修改一下路径:
![在这里插入图片描述](https://img-blog.csdnimg.cn/a6e856548f3842c0bbfac31d8d2fc136.png
编译通过,终于好了。
加载,卸载驱动设备。
p1004 2022.5.18.22.41
需要向系统注册一个字符设备,需要使用函数register_chrdev
,卸载驱动的时候,需要卸载到之前注册的设备。unregister_chrdev
注销字符设备。
设备号
dev_t
高12位为主设备号,低20位为次设备号。
举例:同是IIC设备,同中IIC设备占用不同的次设备号。
次设备号
设备号的操作函数或者宏:从dev_t\获取主设备号或者次设备号,MAJOR(dev_t),MINOR(dev_t)。也可以通过MKDEV(major,minor)获取设备号。
注册字符设备,当用了一个主设备号,剩下把次设备号全部都使用了。
file_operation
结构体:
### 第一个linux设备驱动
/**
*my first driver
*
*/
#include
#include
#include
#include
#include
#include
#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
#include
#include
#include
#include
#include
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
#include
#include
#include
#include
#include
#include
#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
#include
#include
#include
#include
#include
#include
/**
* 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);
}
裸机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灯。
本节的C应用代码
#include
#include
#include
#include
#include
#include
#include
/**
* 1 表示向驱动读取数据
*
* 2 表示向驱动写入数据
*
*/
/**
* ./ledApp < 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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define LED_MAJOR 200 //主设备号
#define LED_NAME "LED" //驱动名称
// static char readbuf[100]; /*读缓冲*/
// static char writebuf[100]; /*读缓冲*/
// static char kernelbuf[100] = {"kernel data"};
/*首先定义寄存器的物理地址*/
#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;
#define LEDOFF 0 /*关闭*/
#define LEDON 1 /*打开*/
static void led_switch(u8 sta)
{
u32 val = 0;
if (sta == LEDON)
{
val = readl(GPIO1_DR);
val &= ~(1<<3);/*bit3 清0 打开led*/
writel(val,GPIO1_DR);
}else if(sta == LEDOFF){
val = readl(GPIO1_DR);
val |= 1<<3;/*bit3 清0 打开led*/
writel(val,GPIO1_DR);
}
}
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_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos)
{
int ret = 0;
unsigned char databuf[1];
ret = copy_from_user(databuf,buf,count);
if (ret<0){
printk("kernel_write error");
return -EFAULT;
}
led_switch(databuf[0]);
return 0;
}
/**
* 字符设备的操作集合
*/
static const struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.write = led_write,
};
static int __init led_init(void)
{
int ret = 0;
u32 val = 0;
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);
/*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);
//初始化led灯。
/**/
ret = register_chrdev(LED_MAJOR,LED_NAME,&led_fops);
if(ret < 0){
printk("LED init failed\n");
return -EIO;
}
return 0;
}
static void __exit led_exit(void)
{
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
unregister_chrdev(LED_MAJOR,LED_NAME);
}
//模块加载函数
module_init(led_init);
//模块卸载
module_exit(led_exit);
MODULE_LICENSE("QHY");