Embedded LINUX driver learning 14 software and hardware separation programming (four) code example (operate the LED light ioctl, set_bit/clear_bit through the test program)

Embedded LINUX driver learning 14 software and hardware separation programming (four) code example (operate the LED light ioctl, set_bit/clear_bit through the test program)

One, hardware information driver

Same as "14 Software and Hardware Separate Programming of Embedded LINUX Driver Learning (3) Code Example (Achieved by struce device member void *platform_data)" ->
1. Code Example (Define hardware information)

2. Code example (software driven)

Features:
After the user space program prints the character device file, the LED light can be turned on and off through the ioctl file, and the LED light status information can be viewed.
The semaphore can prevent the race problem of opening multiple files at the same time, and the bit atom operation can prevent the global variable g_led_state from being accessed. State problem

#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");

3. Test program (user space)

Features:
1. The user controls the LED lights to turn on and off and view the status of the LED lights through commands
Command format:
comm <character device file> <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;

}

Guess you like

Origin blog.csdn.net/weixin_47273317/article/details/108338088