Linux设备驱动第四天(自动创建设备节点、LED驱动程序)

回顾:
与驱动有关的几个重要结构体
1,struct cdev //从软件上代表硬件设备
{
dev_t dev;//设备号 = 主设备号+次设备号
struct file_operations f_ops;
}

2,struct file_operations{
open
read
write
release
ioctrl
mmap
}

3,struct file//对应内核里面的文件表中的一项
{
f_pos;//读写的偏移位置
f_op;//文件的操作函数集合
f_flags;//文件打时传递的参数 读写模式
private_data// 私有数据
…..
}
这里写图片描述

4,struct inode
{
dev_t i_rdev;//设备号
file_opeartions *i_fop;//操作函数集合,内核会给它赋值
struct cdev *i_cdev;//字符设备集合
….
}

以上两个结构体多用于一套驱动代码驱动多个同类型的设备。如下面的案例。

问题:找到串口驱动程序源码文件
KCofnfig:make menuconfig 时生成菜单选项
Makefile:和源码在同一级目录,决定编译动作
make menuconfig
-> Device Drivers
->Character devices
->Samsung S5PV210 Serail port support
宏: CONFIG_SERIAL_S5PV210
路径:devices/serail/Kconfig确定源码在drivers/serail/目录下
打开该路径下的Makefile查找 CONFIG_SERIAL_S5PV210,找到对应的.c文件

Uart案例:
创建设备节点
mknod /dev/serial0 c 204 64 ==> open(“/dev/serial0”); uart_open
mknod /dev/serial1 c 204 65 ==> open(“/dev/serial1”); uart_open
mknod /dev/serial2 c 204 66 ==> open(“/dev/serial2”); uart_open
mknod /dev/serial3 c 204 67 ==> open(“/dev/serial3”); uart_open

其中open(“/dev/serial0”)为应用程序中的代码;
在uart_open中如何确定初始化哪一个串口?
通过设备号来确定初始化哪一个串口。
如何拿到设备号?
在uart_open方法中可以通过传递进来的inode->i_rdev拿到设备号,通过设备号拿到次设备号

在urat_write中如何确定通过哪串口向外发送数据?

#include<linux/init.h>
#include<linux/module.h>

static int uart_open(struct inode *inode, struct file *file)
{
   MINOR(inode -> i_rdev);//次设备号
   //private_data这块空间留给驱动开发人员使用的,放什么数据都可以
   file->private_data = MINOR(inode -> i_rdev);//记录要操作的设备
   //初始化相应寄存器
   return 0; 
}

static int uart_close(struct inode *inode, struct file *file)
{
   printlk("enter uart_close! \n");
   return 0; 
}

static ssize_t uart_write (struct file *file, const char __user *buf, size_t count, loff_t *offset)
{

   return 0; 
}

static ssize_t uart_read(struct file *file,char __user *buf, size_t count, loff_t *offset)
{
   return 0; 
}

static int uart_ioctl(struct inode *inode, struct file *file, unsigned int cmd,unsigned long val)
{
    printlk("enter led_ioctl! \n");
}

//需要实现的函数
static struct file_operations led_fops = {
    .owner   = THIS_MOUDLE,
    .open    = uart_open,
    .release = uart_close,
    .write   = uart_write,
    .read    = uart_read,
    .ioctl   = uart_ioctl,
};

static int major = 0;
/*1,分配一个cdev */
static struct cdev led_cdev;

static int led_status;//记录灯的状态

static int chardevice_init(void){
     dev_t dev;
     if(major != 0){//静态分配
         dev = MKDEV(major,0);//构建一个设备号
         //向内核注册设备号
         register_chrdev_region(dev,1,"tarena");
     }else{
         //动态申请注册设备号 
         alloc_chrdev_region(&dev,1001,"test");
         major = MAJOR(dev);//获取主设备号   相当于dev>>20
         unsigned int minor = MINOR(dev);//获取次设备号  相当于把高20位清0
         printk("major = %d ! \n",major);
     }

     //2, 初始化cdev 
     cdev_init(&led_cdev,&led_fops);

     //3,把cdev添加到内核里面去
     cdev_add(&led_cdev,dev,1);

     //申请GPIO管脚
     gpio_request(S5PV210_GPC1(3),"LED1");//从原图中找管脚名称GPC1_3
     //配置为输出
     gpio_dirction_output(S5PV210_GPC1(3),0);
     led_status = 0;

     return 0;
}

static void chardevice_exit(void){
    dev_t dev;

    //4,按照对内核产生的影响,逆序销毁cdev
    cdev_del(&led_cdev);

    dev = MKDEV(major,0);
    //释放
    unregist_chrdev_region(dev,1);
}

module_init(chardevice_init);
module_exit(chardevice_exit);
MODULE_LICENSE("GPL");

自动创建设备文件
手工创建设备文件:mknode /dev/xxx c major minor
自动创建:在安装模块时自动创建
编程的角度,需要调用以下两个函数:
class_create(….);//生成一个树枝
deivce_create(…)//生成一个果实

class_destroy(...);
device_destroy(...);

其他要完成的工作
1,用busybox添加,要选择支持medv
2,rootfs/ect/rcS添加mount -a
3,rootfs/etc添加fstab文件
4, 在fstab中添加sysfs和proc支持
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
5,在rootfs/ect/rcS中添加
echo /sbin/mdev > / proc/sys/kernel/hotplub

当执行insmod xx.ko时,如果xx_init函数中包含了class_create(THIS_MODULE,”tanena”),生成文件夹:/sys/class/tarena;当执行device_create(cls,NULL,dev,NULL,”leds”),会生成文件夹:/sys/class/tarena/leds;以上操作内核也理解为热插拔事件,产生热插拔事件以后,内核会调用/proc/sys/kernel/hotplug,实则就是调用/sbin/mdev程序,该程序扫描/sys/目录下的变化,根据变化去自动在/dev目录下创建leds文件;

procfs基于内存的文件系统,导出内核的执行信息;如:cat /proc/cpuinfo
sysfs基于内存的文件系统,导出系统中硬件驱动的组织结构;

案例:

#include<linux/init.h>
#include<linux/module.h>

#include<linux/cdev.h>
#include<linux/kernel.h>
#include<linux/device.h>
#include<linux/fs.h>
#include<linux/uaccess.h>
#include<plat/gpio-cfg.h>
#include<asm/gpio.h>

static struce class *cls;//记录树枝 (其实属于一个设备类)

static int led_open(struct inode *inode, struct file *file)
{
   printlk("enter led_open! \n");
   return 0; 
}

static int led_close(struct inode *inode, struct file *file)
{
   printlk("enter led_close! \n");
   return 0; 
}

//假如在用户空间写入:write(fd,buf,1) buf中的值1亮  0 灭
static ssize_t led_write (struct file *file, const char __user *buf, size_t count, loff_t *offset)
{
   int cmd;
   copy_from_user(&cmd,buf,sizeof(cmd));//将用户空间的数据拷贝到内核空间
   if(cmd == 1){
       gpio_set_value(S5PV210_GPC1(3),1);//点亮
       led_status = 1;
   }else{
       gpio_set_value(S5PV210_GPC1(3),-);
       led_status = 0;
   }
   return count; 
}

//read(fd,buf,cnt)
static ssize_t led_read(struct file *file,char __user *buf, size_t count, loff_t *offset)
{
   //把灯的状态返回给用户空间
   copy_to_user(buf,&led_status,sizeof(led_status));
   return count; 
}

static int led_ioctl(struct inode *inode, struct file *file, unsigned int cmd,unsigned long val)
{
    printlk("enter led_ioctl! \n");
}

//需要实现的函数
static struct file_operations led_fops = {
    .owner   = THIS_MOUDLE,
    .open    = led_open,
    .release = led_close,
    .write   = led_write,
    .read    = led_read,
    .ioctl   = led_ioctl,
};

static int major = 0;
/*1,分配一个cdev */
static struct cdev led_cdev;

static int led_status;//记录灯的状态

static int chardevice_init(void){
     dev_t dev;
     if(major != 0){//静态分配
         dev = MKDEV(major,0);//构建一个设备号
         //向内核注册设备号
         register_chrdev_region(dev,1,"tarena");
     }else{
         //动态申请注册设备号 
         alloc_chrdev_region(&dev,1001,"test");
         major = MAJOR(dev);//获取主设备号   相当于dev>>20
         unsigned int minor = MINOR(dev);//获取次设备号  相当于把高20位清0
         printk("major = %d ! \n",major);
     }

     //2, 初始化cdev 
     cdev_init(&led_cdev,&led_fops);

     //3,把cdev添加到内核里面去
     cdev_add(&led_cdev,dev,1);

     //自动创建设备节点文件
     cls =  class_create(THIS_MODULE,"tarena");//见到owner就传THIS_MODULE
     device_create(cls,NULL,dev,NULL,"leds");//这个设备要挂到哪个枝上(cls),父设备为NULL, 

     //申请GPIO管脚
     gpio_request(S5PV210_GPC1(3),"LED1");//从原图中找管脚名称GPC1_3
     //配置为输出
     gpio_dirction_output(S5PV210_GPC1(3),0);
     led_status = 0;

     return 0;
}

static void chardevice_exit(void){
    dev_t dev;
    gpio_free(S5pV210_GCP(3));

    dev = MKDEV(major,0);

     //自动删除设备节点,注意要逆序销毁
     device_destory(cls,dev);//相当于从这个树枝(cls)把水果摘掉(dev)
     class_destory(cls);//砍树枝

    //4,按照对内核产生的影响,逆序销毁cdev
    cdev_del(&led_cdev);

    //释放
    unregist_chrdev_region(dev,1);
}

module_init(chardevice_init);
module_exit(chardevice_exit);
MODULE_LICENSE("GPL");

然后Makefile文件,将ko文件拷贝到开发板,然后在开发板中执行。。。

LED标准程序的写法:
关于uart用户空间编程
串口初始化 xxx_init 或 open
通过串口发送数据 write函数
通过串口接收数据 read函数
修改传输的速率 有效位个数 校验方式 ioctl

led_open(){
gpio_reqiest(….);
gpio_direction_output(…);
}

led_close(){
gpio_free(…);
}

led_ioctl(inode,filep,cmd,val){

if(cmd == 1){//亮
   if(val == 1){//led1_on 第一个灯亮
   }else if(val == 2 ){//led2_on 第二个灯亮
   }
}else if(cmd == 0){

}

}

led_read(){//来获取灯的亮灭状态
int led_status
}

init(){
1,动态申请设备号
2,创建并初始化cdev
3,向内核添加cdev
4,自动创建设备节点文件
}

exit(){
逆序消除init函数中对内核的影响
}

案例:

#include<linux/init.h>
#include<linux/module.h>

#include<linux/cdev.h>
#include<linux/kernel.h>
#include<linux/device.h>
#include<linux/fs.h>
#include<linux/uaccess.h>
#include<plat/gpio-cfg.h>
#include<asm/gpio.h>

#define LED_ON     0x100001;
#define LED_OFF    0x100002;
#define LED_STATUS 0x100003;

static struce class *cls;//记录树枝 (其实属于一个设备类)

static int led_status;//记录灯的状态

static int led_open(struct inode *inode, struct file *file)
{
   //6,申请GPIO管脚
   gpio_request(S5PV210_GPC1(3),"LED1");
   gpio_request(S5PV210_GPC1(4),"LED2");

   //7,配置为输出
   gpio_direction_output(S5PV210_GPC1(3),0);
   gpio_direction_output(S5PV210_GPC1(4),0);
   led_status = 0;
   printlk("enter led_open! \n");
   return 0; 
}

static int led_close(struct inode *inode, struct file *file)
{
   //8,释放GPIO管脚
   gpio_free(S5PV210_GPC1(3));
   gpio_free(S5PV210_GPC1(4));

   printlk("enter led_close! \n");
   return 0; 
}

//read(fd,buf,cnt)
static ssize_t led_read(struct file *file,char __user *buf, size_t count, loff_t *offset)
{
   int ret;
   //把灯的状态返回给用户空间
   ret = copy_to_user(buf,&led_status,sizeof(led_status));
   return count; 
}

/**
*用户空间调用ioctl(fd,cmd,&val);
*产生软中断,调用sys_ioctl
* sys_ioctl(...){
*   led_ioctl(inode,file,cmd,val);
* }
***/

static int led_ioctl(struct inode *inode, struct file *file, unsigned int cmd,unsigned long val)
{
    int index = 0;
    int ret = 0;
    int status = 0;

    //因为不能直接操作用户空间的地址,
    //所以把用户空间的4个字节的数据拷贝到index里面,这就得到了具体操作了哪个灯
    ret = copy_from_user(&index,(int *)val,4);

    if(index > 1 || index <0 ){//只能为0 1,其他的非法
        return -EINVAL;//返回非法参数
    }
    switch(cmd){
        case: LED_ON:
             gpio_set_value(S5PV210_GPC(3)+index,1);
             led_status |= (0x01<<index);
             break;
        case: LED_OFF:
             gpio_set_value(S5PV210_GPC(3)+index,0);
             led_status &= ~(0x01<<index);
             break;
        case: LED_STATUS:
            // printk("status = %d \n",status);
             if(led_status & (0x01<<index)){
                status = 1;
             }else{
                status = 0;
             }
            // printk("status = %d \n",status);
             ret = copy_to_user((int*)val,&status,4);
             break;
        default:
             return -EINVAL;
    }
    return 0;
}

//需要实现的函数
static struct file_operations led_fops = {
    .owner   = THIS_MOUDLE,
    .open    = led_open,
    .release = led_close,
    //.write   = led_write,
    .read    = led_read,
    .ioctl   = led_ioctl,
};

static int major = 0;
/*2,分配一个cdev */
static struct cdev led_cdev;

static int chardevice_init(void){
     dev_t dev;
     //1,动态申请注册设备号 
     alloc_chrdev_region(&dev,1001,"test");
     major = MAJOR(dev);//获取主设备号   相当于dev>>20
     // unsigned int minor = MINOR(dev);//获取次设备号  相当于把高20位清0

     //3, 初始化cdev 
     cdev_init(&led_cdev,&led_fops);

     //4,把cdev添加到内核里面去
     cdev_add(&led_cdev,dev,1);

     //5,自动创建设备节点文件
     cls =  class_create(THIS_MODULE,"tarena");//见到owner就传THIS_MODULE
     //"leds" 就是/dev要生成的设备文件的名字
     device_create(cls,NULL,dev,NULL,"leds");//这个设备要挂到哪个枝上(cls),父设备为NULL

     return 0;
}

static void chardevice_exit(void){

    dev_t dev;
    dev = MKDEV(major,100);

     //自动删除设备节点,注意要逆序销毁
    device_destory(cls,dev);//相当于从这个树枝(cls)把水果摘掉(dev)
    class_destory(cls);//砍树枝

    //10,从内核中删除led_cdev  按照对内核产生的影响,逆序销毁cdev
    cdev_del(&led_cdev);

    //注销设备号
    unregist_chrdev_region(dev,1);
}

module_init(chardevice_init);
module_exit(chardevice_exit);
MODULE_LICENSE("GPL");

对应的测试程序:

#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>

#define LED_ON     0x100001;
#define LED_OFF    0x100002;
#define LED_STATUS 0x100003;

int main(void){
   int fd;
   int cmd = 0;
   int val = ;
   fd = open("/dev/leds",o_RDWR);
   if(fd<0){
      printlf("open /dev/leds failed");
      return -1;
   }

   //LED2 on
   cmd = LED_ON;
   val = 1;//代表第二个灯
   ioctl(fd,cmd,&val);//cmd对应驱动程序ioctl方法中的cmd,val对应驱动程序的val
   cmd = LED_STATUS;
   ioctl(fd,cmd,&val);//cmd对应驱动程序ioctl方法中的cmd,val对应驱动程序的val
   if(val = 1){
       printf("LED2 ON \n");
   }else if(val = 0){
       printf("LED2 OFF \n");
   }
   sleep(5);

   //LED1 OFF
   cmd = LED_OFF;
   val = 1;
   ioctl(fd,cmd,&val);
   cmd = LED_STATUS;
   ioctl(fd,cmd,&val);
   if(val = 0){
      printf("LED2 OFF\n");
   }else{
      printf("LED2 ON\n");
   }
   return 0;
}

作业:计算S5PV210_GPC1(3)的值是多少并验证(打印)

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值