嵌入式LINUX驱动学习之14软硬件分离编程(四)代码举例(通过测试程序操作LED灯 ioctl、set_bit/clear_bit)

嵌入式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;

}

猜你喜欢

转载自blog.csdn.net/weixin_47273317/article/details/108338088