从内核中最简单的驱动程序入手,描述Linux驱动开发,主要文章目录如下(持续更新中):
01 - 第一个内核模块程序
02 - 注册字符设备驱动
03 - open & close 函数的应用
04 - read & write 函数的应用
05 - ioctl 的应用
06 - ioctl LED灯硬件分析
07 - ioctl 控制LED软件实现(寄存器操作)
08 - ioctl 控制LED软件实现(库函数操作)
09 - 注册字符设备的另一种方法(常用)
10 - 一个cdev实现对多个设备的支持
11 - 四个cdev控制四个LED设备
12 - 虚拟串口驱动
13 - I2C驱动
14 - SPI协议及驱动讲解
15 - SPI Linux驱动代码实现
16 - 非阻塞型I/O
17 - 阻塞型I/O
18 - I/O多路复用之 select
19 - I/O多路复用之 poll
20 - I/O多路复用之 epoll
21 - 异步通知
文章目录
&emsp:上一节讲到了LED的硬件分析,得到了LED控制的流程,详见
ioctl LED硬件分析
这一节将接着上一节的分析,对LED控制的软件部分进行实现,本节实现依据的是直接对相关的寄存器进行操作,下一节将利用库函数对LED的软件控制部分进行实现。
1. 预备知识
在驱动中不能对实际的物理地址进行操作,操作的都是虚拟地址。因此需要先了解物理地址和虚拟地址进行转换的相关函数。
1.1 ioremap
将物理地址映射为虚拟地址的函数是ioremap,函数定义在 include/asm/io.h 中,原型如下:
void __iomem *ioremap(phys_addr_t paddr, unsigned long size)
各参数的定义如下
@param1: 要映射的起始的IO地址(物理地址)
@param2: 要映射的空间的大小
@return: 返回映射后的内核虚拟地址(3G-4G). 接着便可以通过读写该返回的内核虚拟地址去访问之这段I/O内存资源
示例:
#define CONF_GPMC_AD4 0x44E10810
void __iomem *conf_gpmc_ad4_vir = NULL;
conf_gpmc_ad4_vir = (unsigned int *)ioremap(CONF_GPMC_AD4, 4);
1.2 readl
内存映射之后,可以通过 readl 函数获取寄存器的状态,函数定义在 include/asm/io.h 中,原型如下
static inline u32 readl(const volatile void __iomem *addr)
各参数的定义如下
@param1: 映射后的内核虚拟地址,ioremap的返回值
@return: 读到的虚拟地址中的值
示例:
readl(conf_gpmc_ad4_vir);
1.3 writel
将内存中的数据读出来后,可以通过 writel 往内存映射的 I/O 空间上写数据,函数定义在 include/asm/io.h 中,原型如下
static inline void writel(u32 b, volatile void __iomem *addr)
各参数的定义如下
@param1: 要写入的数据
@param2: 映射后的内核虚拟地址,ioremap的返回值
@return: 无返回值
示例:
writel(( readl(conf_gpmc_ad4_vir) | 0x7 | 0x1<<5), conf_gpmc_ad4_vir);
// 读出寄存器中的值,设置相应位然后写进去
1.4 iounmap
有映射函数就有相对应的取消映射函数,函数定义在 include/asm/io.h 中,原型如下
static inline void iounmap(volatile void __iomem *addr)
各参数的定义如下
@param1: 映射后的内核虚拟地址,ioremap的返回值
@return: 无返回值
示例:
iounmap(conf_gpmc_ad4_vir);
2. 代码实现
2.1 demo.c
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include "chrdev.h"
int major;
const char *name = "demoname";
struct class *cls;
struct device *dev;
#define CONF_GPMC_AD4 0x44E10810
#define GPIO1_OE 0x4804C134
#define GPIO1_DATAOUT 0x4804C13C
void __iomem *conf_gpmc_ad4_vir = NULL;
void __iomem *conf_gpmc_ad5_vir = NULL;
void __iomem *conf_gpmc_ad6_vir = NULL;
void __iomem *conf_gpmc_ad7_vir = NULL;
void __iomem *gpio1_oe_vir = NULL;
void __iomem *gpio1_dataout_vir = NULL;
int demo_open(struct inode *inode, struct file *filp)
{
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
return 0;
}
int demo_release(struct inode *inode, struct file *filp)
{
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
return 0;
}
ssize_t demo_read(struct file *filp, char __user *userbuf, size_t size, loff_t *offset)
{
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
//copy_to_user(void __user * to, const void * from, size_t n);
return 0;
}
ssize_t demo_write(struct file *filp, const char __user *userbuf, size_t size, loff_t *offset)
{
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
//copy_from_user(void * to, const void __user * from, unsigned long n);
return 0;
}
long demo_ioctl(struct file *filp, unsigned int cmd, unsigned long args)
{
led_node_t led_t;
int ret, led_num;
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
if ( _IOC_TYPE(cmd) != 'd') /* 判断命令与头文件定义的是否一致 */
{
return -ENOTTY; /* not a typewriter */
}
ret = copy_from_user(&led_t, (led_node_t *)args, sizeof(led_t)); //成功返回0,失败返回有多少个Bytes未完成copy。
if (ret)
{
printk("copy_from_user failed.\n");
goto err0;
}
led_num = led_t.which;
switch(cmd)
{
case LEDON:
if (led_num == 1)
{
printk("led1 on.\n");
writel(readl(gpio1_dataout_vir) | (0X1 << 4), gpio1_dataout_vir); // 置1,高电平
}
else if (led_num == 2)
{
printk("led2 on.\n");
writel(readl(gpio1_dataout_vir) | (0X1 << 5), gpio1_dataout_vir); // 置1,高电平
}
else if (led_num == 3)
{
printk("led3 on.\n");
writel(readl(gpio1_dataout_vir) | (0X1 << 6), gpio1_dataout_vir); // 置1,高电平
}
else if (led_num == 4)
{
printk("led4 on.\n");
writel(readl(gpio1_dataout_vir) | (0X1 << 7), gpio1_dataout_vir); // 置1,高电平
}
break;
case LEDOFF:
if (led_num == 1)
{
printk("led1 off.\n");
writel(readl(gpio1_dataout_vir) & (~(0X1 << 4)), gpio1_dataout_vir); // 清0,低电平
}
else if (led_num == 2)
{
printk("led2 off.\n");
writel(readl(gpio1_dataout_vir) & (~(0X1 << 5)), gpio1_dataout_vir); // 清0,低电平
}
else if (led_num == 3)
{
printk("led3 off.\n");
writel(readl(gpio1_dataout_vir) & (~(0X1 << 6)), gpio1_dataout_vir); // 清0,低电平
}
else if (led_num == 4)
{
printk("led4 off.\n");
writel(readl(gpio1_dataout_vir) & (~(0X1 << 7)), gpio1_dataout_vir); // 清0,低电平
}
break;
default:
printk("cmd id error.\n");
}
return 0;
err0:
return ret;
}
const struct file_operations fops = {
.open = demo_open,
.release = demo_release,
.read = demo_read,
.write = demo_write,
.unlocked_ioctl = demo_ioctl,
};
void gpio_iounmap(void)
{
iounmap(gpio1_oe_vir);
}
void gpio_ioremap(void)
{
conf_gpmc_ad4_vir = (unsigned int *)ioremap(CONF_GPMC_AD4, 4);
conf_gpmc_ad5_vir = conf_gpmc_ad4_vir + 4;
conf_gpmc_ad6_vir = conf_gpmc_ad4_vir + 8;
conf_gpmc_ad7_vir = conf_gpmc_ad4_vir + 12;
gpio1_oe_vir = (unsigned int *)ioremap(GPIO1_OE, 4);
gpio1_dataout_vir = gpio1_oe_vir + 8;
}
void led_init(void)
{
gpio_ioremap();
writel(( readl(conf_gpmc_ad4_vir) | 0x7 | 0x1<<5), conf_gpmc_ad4_vir); /* 引脚复用,选择MOD7,即GPIO */
writel(( readl(conf_gpmc_ad5_vir) | 0x7 | 0x1<<5), conf_gpmc_ad5_vir);
writel(( readl(conf_gpmc_ad6_vir) | 0x7 | 0x1<<5), conf_gpmc_ad6_vir);
writel(( readl(conf_gpmc_ad7_vir) | 0x7 | 0x1<<5), conf_gpmc_ad7_vir);
writel(( readl(gpio1_oe_vir) & (~(0xF << 4)) ), gpio1_oe_vir); /* GPIO1_4~GPIO1_7引脚设置为输出功能(0表示输出) */
}
static int __init demo_init(void)
{
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
major = register_chrdev(0, name, &fops);
if (major <= 0)
{
printk("register_chrdev failed.\n");
goto err0;
}
cls = class_create(THIS_MODULE, "char_class");
if (cls == NULL)
{
printk("class_create failed.\n");
goto err1;
}
dev = device_create(cls, NULL, MKDEV(major, 0), NULL, "chrdev%d", 0);
if (dev ==NULL)
{
printk("device_create failed.\n");
goto err2;
}
led_init(); // 设置为输出模式
return 0;
err2:
class_destroy(cls);
err1:
unregister_chrdev(major, name);
err0:
return major;
}
static void __exit demo_exit(void)
{
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
gpio_iounmap(); //解除映射
device_destroy(cls, MKDEV(major, 0));
class_destroy(cls);
unregister_chrdev(major, name);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
2.2 test.c
#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "chrdev.h"
const char *pathname = "/dev/chrdev0";
#define LED_NUM 4
int main(void)
{
int fd, i;
led_node_t led;
fd = open(pathname, O_RDWR, 0666);
if (fd <= 0)
{
printf("open failed.\n");
return -1;
}
for (i=1; i<=LED_NUM; i++)
{
led.which = i;
led.status = 0; // 0表示灭
ioctl(fd, LEDOFF, &led);
sleep(1);
led.status = 1; // 1表示亮
ioctl(fd, LEDON, &led);
sleep(1);
}
close(fd);
return 0;
}
2.3 chrdev.h
#ifndef _CHRDEV_H_
#define _CHRDEV_H_
typedef struct led_node
{
int which;
int status;
}led_node_t;
#define LED_MAGIC 'q'
#define LEDON _IOW(LED_MAGIC, 0, struct led_node)
#define LEDOFF _IOW(LED_MAGIC, 1, struct led_node)
#endif /* chrdev.h */
2.4 Makefile
KERNELDIR ?= /home/linux/ti-processor-sdk-linux-am335x-evm-05.02.00.10/board-support/linux-4.14.79/
PWD := $(shell pwd)
all:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) modules
arm-linux-gnueabihf-gcc test.c -o app
install:
sudo cp *.ko app /tftpboot
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
rm app
clean:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
rm app
obj-m += demo.o
2.5 输出结果
root@am335x-evm:~# insmod demo.ko
[ 3485.051113] demo_init -- 169.
root@am335x-evm:~# ./app
[ 3486.920446] demo_open -- 28.
[ 3486.923853] demo_ioctl -- 81.
[ 3486.927475] led1 off.
[ 3487.930225] demo_ioctl -- 81.
[ 3487.934385] led1 on.
[ 3488.936823] demo_ioctl -- 81.
[ 3488.939868] led2 off.
[ 3489.948584] demo_ioctl -- 81.
[ 3489.951628] led2 on.
[ 3490.960095] demo_ioctl -- 81.
[ 3490.964276] led3 off.
[ 3491.966745] demo_ioctl -- 81.
[ 3491.969787] led3 on.
[ 3492.972221] demo_ioctl -- 81.
[ 3492.975268] led4 off.
[ 3493.977737] demo_ioctl -- 81.
[ 3493.980786] led4 on.
[ 3494.992661] demo_release -- 35.
root@am335x-evm:~# rmmod demo.ko
[ 3503.501455] demo_exit -- 206.