目标
本次实验将为开发上的LED灯实现驱动代码,应用程序可以通过ioctl系统调用控制LED灯的亮灭。
硬件资源
处理器
本人使用的开发板是讯为科技的iTop4412开发板,CPU是三星的4412,属于A9系列。
原理图
在开发板的原理图中,可以看到LED是连到GPL2_0引脚的,因此我们要控制LED,就要控制这个引脚。
寄存器物理地址转化成虚拟地址
翻看4412的芯片手册,可以找到控制led的I/O口的相关寄存器,我们需要对其进行设置。
不像裸机代码里,控制某个寄存器可以使用物理地址,在Linux系统里,用户用到的所有地址内核都认为是虚拟地址。内核看到一个地址以后把它当成虚拟地址,当内核需要读写这些地址时,需要经过内存管理子系统翻译成物理地址再读写。因此我们想要控制I/O寄存器,需要将寄存器的地址转化成虚拟地址,然后再通过虚拟地址控制这些寄存器。
内核中提供了如下宏将物理地址转化成虚拟地址:
ioremap(physaddr,size);
physaddr 是待转化的物理地址,size是物理地址的长度,返回虚拟地址。
内核还提供了若干函数给相关虚拟地址写数据:
void writel(unsigend int data,volatile void _iomem *addr);
代码实现
根据字符设备驱动,可以编写基于字符设备驱动模型的LED设备驱动,代码如下:
驱动代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/io.h>
/*设备描述符*/
struct cdev led_dev;
/*设备号*/
dev_t devno;
/*控制寄存器地址*/
#define LEDCON 0x11000100
#define LEDDAT 0x11000104
/*控制器转换后的虚拟地址*/
unsigned int *led_con;
unsigned int *led_dat;
/*打开函数*/
int led_open(struct inode *inode,struct file *file){
led_con = ioremap(LEDCON,4);
led_dat = ioremap(lEDDAT,4);
writel(0x00000001,led_con);
return 0;
}
/*控制函数*/
long led_ioctl(struct file *file,unsigned int cmd,unsigned long arg){
switch(cmd){
//点亮led
case 1: writel(0x01,led_dat);
break;
//熄灭led
case 0: writel(0x00000000,led_dat);
break;
default:
return -EINVAL;
}
return 0;
}
/*关闭函数*/
int led_close(struct inode *inode, struct file *file){
return 0;
}
/*文件操作函数集合*/
struct file_operations led_ops = {
.open = led_open,
.unblocked_ioctl = led_ioctl,
.release = led_close,
};
/*模块初始化*/
static int led_init(void){
int ret;
//初始化cdev
cdev_init(&led_dev,&led_ops);
//分配设备号
alloc_chrdev_region(&devno,0,1,"myled");
//注册驱动
ret = cdev_add(&led_dev,devno,1);
return ret;
}
/*模块注销*/
static void led_exit(void){
//注销设备结构
cdev_del(&led_dev);
//注销设备号
unregister_chrdev_region(&led_dev,2);
}
/*遵循协议声明*/
MODULE_LICENSE("GPL");
/*模块安装和卸载函数*/
module_init(led_init);
module_exit(led_exit);
应用代码
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <fcntl.h>
int main(int arg,char *argv[]){
int fd;
int i=0;
if((fd=open("/dev/myled",O_RDWR))<0){
printf("App open file failed!\n");
return 0;
}
while(i++ < 4){
ioctl(fd,0);
sleep(1);
ioctl(fd,1);
sleep(1);
}
return 0;
}