嵌入式LINUX驱动学习之14软硬件分离编程(四)代码举例(通过测试程序操作LED灯 ioctl、set_bit/clear_bit)
一、硬件信息驱动程序
同 《嵌入式LINUX驱动学习之14软硬件分离编程(三)代码举例(通过struce device 成员void *platform_data 实现)》 ->
一、代码举例(定义硬件信息)
二、代码举例(软件驱动)
功能:
用户空间程序打字符设备文件后,可以通过ioctl文件打开、关闭LED灯,查看LED灯状信息,
通过信号量防止同时打开多个文件的竟态问题,通过位原子操作防止全局变量g_led_state访问的竟态问题
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/atomic.h>
#include <linux/semaphore.h>
/*定义LED灯的三个控制命令,需要和用户空间值一致 */
#define LED_ON 0X1000
#define LED_OFF 0X1001
#define LED_STATE 0X1010
#define LED_NUM 4 //定义LED灯的数量
/*保存硬件信息*/
struct led_phy_struct {
unsigned long phy_addr;
int gpio;
};
struct led_phy_struct *led_phy_obj = NULL; //用于保存硬件信息对象,NULL表示暂时没有对应的硬件信息
void * ioremap_ret;//保存ioremap()函数的返回值
unsigned long *base,*outenb,*altfn;//保存寄存器映射到内存虚拟空间地址的首地址
unsigned long g_led_state = 0xf;//数组即二进制:1111,用于保存LED灯的状,1表示关闭,0表示开启,配合位原子操作函数作用;
struct semaphore sema_led;//定义信号量对象,用于避免竟太访问问题
/*操作LED灯的函数,形参num表示要开启的LED灯的编号;*/
void _led_on(int num){
ioremap_ret = ioremap(led_phy_obj[num-1].phy_addr,0x24);
base = (unsigned long *)ioremap_ret;
*base &= ~(0x1 << led_phy_obj[num-1].gpio) ;
outenb = (unsigned long *)(ioremap_ret + 0x4);
*outenb |= (0x1 << led_phy_obj[num-1].gpio) ;
if(led_phy_obj[num-1].gpio <16){
altfn = (unsigned long *)(ioremap_ret + 0x20);
*altfn &= ~(0x3 << (led_phy_obj[num-1].gpio *2)) ;
*altfn |= (0x1 << (led_phy_obj[num-1].gpio *2)) ;
}
else {
altfn = (unsigned long *)(ioremap_ret + 0x24);
*altfn &= ~(0x3 << ((led_phy_obj[num-1].gpio - 16) *2)) ;
*altfn |= (0x1 << ((led_phy_obj[num-1].gpio - 16) * 2)) ;
}
iounmap(ioremap_ret);
clear_bit(num-1,&g_led_state);//位原子操作,设置全局变量g_led_state的num-1位为0;
}
/*操作LED灯,led_num[0] = 0表示全部开启,1~4表示开启对应的LED灯*/
void led_on(unsigned long led_num[LED_NUM]){
if(led_num[0] != 0)
_led_on(led_num[0]);
else {
int i = 0;
while(led_phy_obj[i].phy_addr !=0){
_led_on(i+1);
i ++ ;
}
}
}
/*操作LED灯的函数,形参num表示关闭的LED灯的编号;*/
void _led_off(int num){
ioremap_ret = ioremap(led_phy_obj[num-1].phy_addr,0x24);
base = (unsigned long *)ioremap_ret;
*base |= (0x1 << led_phy_obj[num-1].gpio) ;
iounmap(ioremap_ret);
set_bit(num-1,&g_led_state); //位原子操作,设置g_led_state的num-1位为1,
}
/*操作LED灯,led_num[0] = 0表示全部关闭,1~4表示关闭对应的LED灯*/
void led_off(unsigned long led_num[LED_NUM]){
if(led_num[0] != 0)
_led_off(led_num[0]);
else {
int i = 0;
while(led_phy_obj[i].phy_addr !=0){
_led_off(i+1);
i ++ ;
}
}
}
/*查看LED灯状态,num表示要查看的LED灯编号,led_num数组用于保存查看的信息;
当全局变量g_led_state对应的数据位为1表示LED灯为关闭,为0表示LED灯为开户*/
void _led_state(int num,unsigned long led_num[LED_NUM]){
if(test_bit(num-1,&g_led_state))
led_num[num-1] = 1;
else
led_num[num-1] = 0;
}
/*查看LED灯状态,led_num[0] = 0表示全部查看,1~4表示关闭对应的LED灯*/
void led_state(unsigned long led_num[LED_NUM]){
int i = 0;
if(led_num[0] != 0 )
_led_state(led_num[0],led_num);
else
while(led_phy_obj[i].phy_addr != 0){
_led_state(i+1,led_num);
i ++;
}
}
//ioctl操作函数
static long led_ioctl_func(struct file * file ,\
unsigned int ucmd, unsigned long ubuf){
unsigned long led_num[LED_NUM];//定义LED灯的数组
int copy_ret;//保存copy_to_user()/copy_from_user()函数返回值
/*判断硬件驱动是否和软件驱动匹配,如果没有匹配,led_phy_obj地址为NULL,
此时不能进行LED灯操作,用户空间程序返回一个错误代码,结束程序*/
if(!led_phy_obj){
printk("硬件驱动不存在\n");
return -EIO;
}
/*将用户空间ubuf地址的数据拷贝到内核空间,长度为sizeof(unsigned long) * 4 */
copy_ret = copy_from_user(led_num,(unsigned long *)ubuf,sizeof(unsigned long) * 4);
/*判断用户空间发送过来的LED灯操作命令*/
switch(ucmd){
case LED_ON :
led_on(led_num);
break;
case LED_OFF :
led_off(led_num);
break;
case LED_STATE :
led_state(led_num);
/*拷贝内核空间led_num数组为首地址,长度为sizeof(unsigned long) * 4的数据到用户空间*/
copy_ret = copy_to_user((unsigned long *)ubuf,led_num,sizeof(unsigned long) * 4);
break;
default :
break;
}
return 0;
}
/*当用户空间执行open()函数打开字符设备文件时,内核空间执行如下函数:*/
static int led_open(struct inode * inode , struct file * file){
down(&sema_led);//获取信号量,避免竟态问题
return 0;
}
/* 当用户空间执行close()函数关闭字符设备文件时,内核空间执行如下函数:*/
static int led_close(struct inode * inode , struct file * file){
up(&sema_led);//文件关闭,释放信号量
return 0;
}
struct file_operations f_ops = {
.owner = THIS_MODULE,
.unlocked_ioctl = led_ioctl_func,//用户空间ioctl函数
.open = led_open,//用户空间open函数
.release = led_close//用户空间close函数
};
/*定义混杂设备对象*/
struct miscdevice misc_ops = {
.minor = MISC_DYNAMIC_MINOR,
.name = "myled",//定义字符设备名称,即创建成功后,会在/dev目录下创建myled混杂设备文件
.fops = &f_ops
};
/*当硬件驱动和软件驱动匹配成功执行*/
int probe_led(struct platform_device * pd){
led_phy_obj = (struct led_phy_struct *) pd ->dev.platform_data;
return 0;
}
/*当匹配成功的卸载硬件驱动、或当前软件驱动时执行*/
int remove_led(struct platform_device * pd){
led_phy_obj = NULL;//防止驱动卸载后,用户空间继续打开文件,出现错误
return 0;
}
/* struct platform_driver对象*/
struct platform_driver pd_obj = {
.probe = probe_led,
.remove = remove_led,
.driver = {
.name = "LED_",//和硬件驱动的匹配名称,只匹配完全一致的硬件驱动
}
};
static int myled_ioctl_init(void){
platform_driver_register(&pd_obj);//注册软件驱动对象
misc_register(&misc_ops);//注册混杂设备对象
sema_init(&sema_led,1);//初始为信号量,只能同时一个用户程序操作此混杂设备
return 0;
}
static void myled_ioctl_exit(void){
platform_driver_unregister(&pd_obj);//卸载软件驱动对象
misc_deregister(&misc_ops);//卸载混杂设备对象
}
module_init(myled_ioctl_init);
module_exit(myled_ioctl_exit);
MODULE_LICENSE("GPL");
三、测试程序(用户空间)
功能:
1、用户通过命令控制 LED灯的开、关和查看LED灯的状态
命令格式:
comm <字符设备文件> < on | off | state> [0~4]
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
/*定义LED灯三个命令的值*/
#define LED_ON 0X1000
#define LED_OFF 0X1001
#define LED_STATE 0X1010
/*打印LED灯状态信息*/
void led_state_print(unsigned long led_num,unsigned long ubuf[4]){
int i = 4;
if(led_num)
printf("LED%d状态为%s\n",led_num,ubuf[led_num-1] ? "关闭" : "开启");
else
while(i){
printf("LED%d状态为%s\n",4-i+1,ubuf[4-i] ? "关闭" : "开启");
i --;
}
}
int main(int argc , char *argv[]){
int fp ; //保存打开文件的返回值,确定文件打开是否成功
int ioctl_ret;//保存ioctl函数的返回值,确定ioctl函数是否成功
unsigned long led_num = 0;//保存LED灯的编号,0代表全部,1~4代表LED1~LED4
unsigned int ucmd = 0;//保存对LED灯的操作命令:on off state
unsigned long ubuf[4] = {
0};//用于发送或接收LED灯的信息
if((argc < 3) || (argc > 4)) //判断命令是否正确
goto comm_err; //命令错误时跳转至 comm_err :
fp = open(argv[1],O_RDWR);//打开字符设备文件
if(fp < 0)//判断打开字符设备文件是否蒽
goto fp_err;//字符设备文件打开失败跳转到fp_err
/* 当输入了LED灯的编号时,将LED灯的编号转换为数字0~4 */
if(argc == 4){
led_num = strtoul(argv[3],NULL,0);
if((led_num < 0 )|| (led_num > 4))
goto led_num_err;
}
ubuf[0] = led_num; //保存要操作的LED灯编号
/* 判断输入的命令是否为on,当为on时,将LED_ON赋值给ucmd ,off state 类同 */
if(!strcmp(argv[2],"on"))
ucmd = LED_ON;
else if(!strcmp(argv[2],"off"))
ucmd = LED_OFF;
else if(!strcmp(argv[2],"state"))
ucmd = LED_STATE;
else {
//当输入的不是on | off | state,关闭打开的文件,跳转到 comm_err :
close(fp);
goto comm_err;
}
ioctl_ret = ioctl(fp,ucmd,ubuf); //执行ioctl()函数
if(ioctl_ret != 0) //当ioctl()函数执行失败时,跳转至ioctl_err
goto ioctl_err;
/*当输入的是state命令时,ioctl函数执行完毕后,打印对应LED灯状态信息 */
if(ucmd == LED_STATE)
led_state_print(led_num,ubuf);
sleep(30);//可以删除,主要用于测试竟态问题
close(fp);
return 0;
comm_err :
printf("命令错误!\n");
printf(" comm <cdev_file> <on|off|state> [led_num]\n");
return -1;
fp_err :
perror("file_err:");
return -1;
ioctl_err :
perror("ioctl_err:");
close(fp);
return -1;
led_num_err :
close(fp);
printf("LED灯编号为1 ~4 \n");
return -1;
}