linux驱动开发_linux驱动开发详解 - CSDN
精华内容
参与话题
  • Linux驱动开发入门——基本知识简介

    千次阅读 2017-06-22 10:02:08
    Linux设备驱动的基本概念 设备驱动程序(Device Driver),简称驱动程序(Driver)。它是一个允许计算机软件与硬件交互的程序。这种程序建立了一个硬件与硬件,或硬件与软件沟通的界面。CPU经由主板上的总线(Bus...

    1、Linux设备驱动的基本概念


    设备驱动程序(Device Driver),简称驱动程序(Driver)。它是一个允许计算机软件与硬件交互的程序。这种程序建立了一个硬件与硬件,或硬件与软件沟通的界面。CPU经由主板上的总线(Bus)或其他沟通子系统(Subsystem)与硬件形成连接,这样的连接使得硬件设备之间的数据交换成为可能。


    驱动程序是提供硬件到操作系统的一个接口,并且协调二者之间的关系。


    计算机系统的主要硬件由CPU存储器外部设备组成。驱动程序的对象一般是存储器和外部设备。Linux将这些设备分为3大类,分别是字符设备块设备网络设备

    1、字符设备

    字符设备是指那些能一个字节一个字节读取数据的设备,如LED灯、键盘、鼠标等。字符设备一般需要在驱动层实现open()、close()、read()、write()、ioctl()等函数。这些函数最终将被文件系统中的相关函数调用。内核为字符设备对应一个文件,/dev/console。对字符设备的操作可以用个字符设备文件/dev/console来进行。

    2、块设备

    在linux系统中,进行块设备读写时,每次只能传输一个或者多个块。

    3、网络设备

    网络设备主要负责主机之间的数据交换。



    用户态和内核态

    用户态处理上层的软件工作。

    内核态用来管理用户态的程序,完成用户态请求的工作。


    模块机制

    模块是可以在运行时加入内核的代码。模块在内核启动时装载称为静态装载,在内核已经运行时装载称为动态装载。


    驱动开发需掌握的知识:

    1、C

    2、硬件基础。不要求设计电路,但对芯片手册上描述的接口设备有清楚的认识。比如SRAM、Flash、UART、IIC、USB等。

    3、Linux内核源代码。一些重要的数据结构和函数。

    4、多任务程序设计的能力。


    驱动开发与应用开发的差异:

    1、内核及驱动程序开发时不能访问C库。因为C库是使用内核中的系统调用来实现的,而且是在用户空间实现的。

    2、内核及驱动程序开发时必须使用GNU C,因为Linux从一开始就使用GNU C。

    3、内核支持异步终端、抢占和SMP,故必须注意同步和并发。

    4、内核只有一个很小的定长堆栈。

    5、内核及驱动程序开发时缺乏像用户空间那样的内存保护机制。

    6、内核及驱动程序开发时浮点数很难使用,应该使用整形数。

    7、内核及驱动程序开发要考虑可移植性。


    2、Linux 源码结构分析

    1、arch目录

    包含与体系结构相关的代码,每一种平台都有一种相应的目录。


    2、drivers目录

    包含了Linux内核支持的大部分驱动程序。


    3、fs目录

    所有文件系统相关的代码。



    4、其他目录




    3、内核配置选项:

    为简化内核源代码的编译,有如下机制:

    1、Makefile 文件:它的作用是根据配置的情况,构造出需要编译的源文件列表,然后分别编译,并把目标代码链接到一起,最终形成Linux内核二进制文件。由于Linux内核源代码是按照树形结构组织的,所以Makefile也被分布在目录树中。

    2、Kconfig 文件:它的作用是为用户提供一个层次化的配置选项集。make menuconfig 命令通过分布在各个子目录中的Kconfig 文件构建配置用户界面。

    3、配置文件(.config):当用户配置完后,将配置信息保存在.config 文件中。

    4、配置工具:包括配置命令解释器和配置用户界面。


    当执行menuconfig 命令时,配置程序会依次从目录由浅入深查找每一个Kbuild文件,依照这个文件中的数据生成一个配置菜单。Kbuild像是一个分布在各个目录中的配置数据库,通过这个数据库可以生成配置菜单。在配置菜单中根据需要配置完成后会在主目录下生成一个.config文件,此文件保存了配置信息。

    然后执行make命令,会依赖生成的.config文件,以确定哪些功能将编译入内核中,哪些功能不编译入内核中。然后递归地进入每一个目录,寻找Makefile文件,编译相应的代码。


    3.1、常规配置: 包含关于内核的大量配置,(代码成熟度、版本信息、模块配置)





    3.2、版本信息:



    3.3、模块配置:


    3.4、块设备层配置:包含对系统使用的块设备的配置,主要包含调度器的配置,硬盘设备的配置。




    3.5、CPU类型和特性配置:






    3.6、电源管理配置:





    3.7、网络配置:







    3.8、设备驱动配置:

    通用设备:






    字符设备配置:





    多媒体设备驱动配置:




    USB设备驱动配置:





    3.9、文件系统配置:





    4、嵌入式文件系统基本知识:

    Linux支持多种文件系统,包括 ext2、ext3、vfat、ntfs、iso9660、jffs、romfs、cramfs、nfs 等。为了统一管理,Linux引入虚拟文件系统 VFS(Virtual FILE System)。


    Linux 文件系统由 4 层组成,分别是用户层、内核层、驱动层、硬件层

    用户层:为用户提供一个操作接口。

    内核层:实现了各种文件系统。

    驱动层:是块设备的驱动程序。

    硬件层:是嵌入式系统使用的几种存储器。


    Linux启动时,第一个必须挂载的是  根文件系统


    嵌入式系统的存储介质



    JFFS文件系统:主要用于NOR型Flash存储器。其基于MTD驱动层。可读写、支持数据压缩、基于哈希表的日志型文件系统,并提供了崩溃掉电安全保护,提供“写平衡”支持。

    YAFFS文件系统:专门为NAND Flash存储器设计的嵌入式文件系统。适用于大容量的存储设备。速度快,占用内存少,不支持压缩和只支持NAND Flash存储器。


    根文件系统

    根文件系统被存储在Flash存储器中,存储器被分为多个分区,(分区1,分区2,分区3等。)

    分区1一般存储Linux内核映像文件分区2存放根文件系统,根文件系统中存放着系统启动必须的文件和程序(包括提供用户界面的shell程序、应用程序依赖的库、配置文件等)。


    内核启动后运行的第一个程序是init,其将启动根文件系统中的shell程序,给用户提供一个友好的操作界面。





    构建根文件系统:

    第一种方法:下载相应的命令源码,并移植到处理器架构平台上。

    第二种方法:使用开源工具构建。(BusyBox、TinyLogin、Embutils)






    展开全文
  • linux驱动编写(总结篇)

    万次阅读 多人点赞 2019-08-14 06:56:47
    01、linux驱动编写(入门) 02、linux驱动编写(虚拟字符设备编写) 03、linux驱动编写(字符设备编写框架) 04、linux驱动编写(Kconfig文件和Makefile文件) 05、linux驱动编写(块设备驱动代码) 06、linux...

    【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com

     

    01、linux驱动编写(入门)

    02、linux驱动编写(虚拟字符设备编写)

    03、linux驱动编写(字符设备编写框架)

    04、linux驱动编写(Kconfig文件和Makefile文件)

    05、linux驱动编写(块设备驱动代码)

    06、linux驱动编写(platform总线和网卡驱动)

    07、linux驱动编写(usb host驱动入门)

    08、linux驱动编写(声卡驱动之asoc移植)

    09、linux驱动编写(sd卡驱动)

    10、linux驱动编写(摄像头驱动)

    11、linux驱动编写(nandflash驱动)

    12、linux驱动编写(dma驱动)

    13、linux驱动编写(电源管理驱动)

    14、linux驱动编写(看门狗)

    15、linux驱动编写(lcd驱动)

    16、linux驱动编写(触摸屏驱动)

    17、linux驱动编写(pwm驱动)

    18、linux驱动编写(其他的驱动代码)

     

    ps:

    a, drivers目录下面协议和功能交叉在一起,sound目录独立在外,这些都需要分开一下。

    b,选择开发板的时候可以选择一些大牌子的开发板,比如zlg或者友善电子的板子。

    c,如果是学习,那么开发的板子以性能够用为主,比如arm7、arm9。如果是性能调优,还是尽量arm性能高一些为好,比如a53、a56,甚至是a72、a73。

    d,linux下面的驱动会屏蔽掉很多的硬件细节,建议可以先学习一下stm32下面各个外设的一般处理方法,再回来处理linux驱动就会达到很好的效果。

     

     

     

    展开全文
  • linux驱动开发架构

    千次阅读 2019-07-30 16:57:29
    最近开始开发驱动,现总结通用驱动开发模型如下 驱动整体模型: 添加一个设备,多数需要用户空间下发指令等操作。那么有两个问题: kernel如何控制设备 用户空间如何和kernel中的驱动交互 问题1: kernel中有各种...

    驱动模型

    最近开始开发驱动,现总结通用驱动开发模型如下
    驱动整体模型:
    在这里插入图片描述
    添加一个设备,多数需要用户空间下发指令等操作。那么有两个问题:

    1. kernel如何控制设备
    2. 用户空间如何和kernel中的驱动交互

    问题1:
    kernel中有各种总线,设备挂载在总线上,驱动通过kernel总线提供的接口初始化控制设备。
    问题2:
    kernel中提供文件设备驱动,在驱动中增加一个文件设备,如字符设备、proc、sys等文件设备。

    基于以上两个问题,驱动包含两部分
    在这里插入图片描述

    开发设备驱动

    系统端驱动开发步骤

    1、阅读设备相关的规格书、demo
    2、确定设备挂载总线、文件交互设备
    3、参照demo,编写驱动代码,代码包含两部分:设备树添加结点、逻辑代码

    注意: 设备树结点中的字段可以是标准的(内核已有代码解析),也可以包含自定义的(设备驱动逻辑代码解析)。

    设备端基于单片机驱动开发

    设备端硬件形态有两种:
    1、设备端有一个独立的小系统,通过一个单片机运行
    2、设备端仅由电子原件和电路图实现

    形态1:
    一般情况下,该部分代码,设备厂商已提供,无需系统端负责开发。系统端通过总线和设备进行交互。

    形态2:
    设备端上电后,驱动实现初始化,控制设备端寄存器,配置设备以满足对设备功能、数据的需求

    应用场景

    以上描述的驱动开发架构模式,常用于应用级驱动开发。一些控制器类型,如i2c控制器、spi总线控制器等kernel中框架层的,一般不需要文件系统设备的存在,只需要开发设备驱动即可;为debug方便,一般也会搭配文件系统设备,方便命令行查看设备状态。

    在开发中,可以根据实际需求决定。

    DEMO

    需求描述

    给一个i2c编写驱动,i2c提供一些接口给userspace使用

    添加设备树结点
    // SoC上的i2c控制器的地址
    i2c@138B0000 {
    	#address-cells = <1>;
        #size-cells = <0>;
        samsung,i2c-sda-delay = <100>;
        samsung,i2c-max-bus-freq = <20000>;
        pinctrl-0 =<&i2c5_bus>;
        pinctrl-names="default";
        // 这个一定要okay,其实是对"./arch/arm/boot/dts/exynos4.dtsi +387"处的status = "disabled"的重写,
        // 相同的节点的不同属性信息都会被合并,相同节点的相同的属性会被重写
        status="okay";
        // 设备子节点,/表示板子,它的子节点node1表示SoC上的某个控制器,
        // 控制器中的子节点node2表示挂接在这个控制器上的设备(们)。68即是设备地址。
        // 父结点是一个i2c总线,在此处定义i2c设备,设备i2c客户端自动和总线关联;
        // 否则,多个总线,设备i2c客户端驱动如何和总线关联?(待学习了解)
        mpu6050@68{
        	// 这个属性就是我们和驱动匹配的钥匙,一个字符都不能错
            compatible="invensense,mpu6050";
            // 这个属性是从设备的地址,我们可以通过查阅手册"MPU-6050_DataSheet_V3_4"得到
            reg=<0x68>;
        };
    };
    
    驱动实现
    //mpu6050_common.h
    #define MPU6050_MAGIC 'K'
    
    union mpu6050_data
    {
        struct {
            short x;
            short y;
            short z;
        }accel;
        struct {
            short x;
            short y;
            short z;
        }gyro;
        unsigned short temp;
    };
    
    #define GET_ACCEL _IOR(MPU6050_MAGIC, 0, union mpu6050_data)
    #define GET_GYRO  _IOR(MPU6050_MAGIC, 1, union mpu6050_data) 
    #define GET_TEMP  _IOR(MPU6050_MAGIC, 2, union mpu6050_data)
    
    //mpu6050_drv.h
    
    #define SMPLRT_DIV      0x19    //陀螺仪采样率,典型值:0x07(125Hz)
    #define CONFIG          0x1A    //低通滤波频率,典型值:0x06(5Hz)
    #define GYRO_CONFIG     0x1B    //陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s)
    #define ACCEL_CONFIG        0x1C    //加速计自检、测量范围及高通滤波,典型值:0x18(不自检,2G,5Hz)
    #define ACCEL_XOUT_H        0x3B
    #define ACCEL_XOUT_L        0x3C
    #define ACCEL_YOUT_H        0x3D
    #define ACCEL_YOUT_L        0x3E
    #define ACCEL_ZOUT_H        0x3F
    #define ACCEL_ZOUT_L        0x40
    #define TEMP_OUT_H      0x41
    #define TEMP_OUT_L      0x42
    #define GYRO_XOUT_H     0x43
    #define GYRO_XOUT_L     0x44
    #define GYRO_YOUT_H     0x45
    #define GYRO_YOUT_L     0x46
    #define GYRO_ZOUT_H     0x47    //陀螺仪z轴角速度数据寄存器(高位)
    #define GYRO_ZOUT_L     0x48    //陀螺仪z轴角速度数据寄存器(低位)
    #define PWR_MGMT_1      0x6B    //电源管理,典型值:0x00(正常启用)
    #define WHO_AM_I        0x75    //IIC地址寄存器(默认数值0x68,只读)
    #define SlaveAddress        0x68    //MPU6050-I2C地址寄存器
    #define W_FLG           0
    #define R_FLG           1
    
    //mpu6050.c
    struct mpu6050_pri {
        struct cdev dev;
        struct i2c_client *client;
    };
    struct mpu6050_pri dev;
    static void mpu6050_write_byte(struct i2c_client *client,const unsigned char reg,const unsigned char val)
    { 
        char txbuf[2] = {reg,val};
        struct i2c_msg msg[2] = {
            [0] = {
                .addr = client->addr,
                .flags= W_FLG,
                .len = sizeof(txbuf),
                .buf = txbuf,
            },
        };
        i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
    }
    static char mpu6050_read_byte(struct i2c_client *client,const unsigned char reg)
    {
        char txbuf[1] = {reg};
        char rxbuf[1] = {0};
        struct i2c_msg msg[2] = {
            [0] = {
                .addr = client->addr,
                .flags = W_FLG,
                .len = sizeof(txbuf),
                .buf = txbuf,
            },
            [1] = {
                .addr = client->addr,
                .flags = I2C_M_RD,
                .len = sizeof(rxbuf),
                .buf = rxbuf,
            },
        };
    
        i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
        return rxbuf[0];
    }
    static int dev_open(struct inode *ip, struct file *fp)
    {
        return 0;
    }
    static int dev_release(struct inode *ip, struct file *fp)
    {
        return 0;
    }
    static long dev_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
    {
        int res = 0;
        union mpu6050_data data = {{0}};
        switch(cmd){
        case GET_ACCEL:
            data.accel.x = mpu6050_read_byte(dev.client,ACCEL_XOUT_L);
            data.accel.x|= mpu6050_read_byte(dev.client,ACCEL_XOUT_H)<<8;
            data.accel.y = mpu6050_read_byte(dev.client,ACCEL_YOUT_L);
            data.accel.y|= mpu6050_read_byte(dev.client,ACCEL_YOUT_H)<<8;
            data.accel.z = mpu6050_read_byte(dev.client,ACCEL_ZOUT_L);
            data.accel.z|= mpu6050_read_byte(dev.client,ACCEL_ZOUT_H)<<8;
            break;
        case GET_GYRO:
            data.gyro.x = mpu6050_read_byte(dev.client,GYRO_XOUT_L);
            data.gyro.x|= mpu6050_read_byte(dev.client,GYRO_XOUT_H)<<8;
            data.gyro.y = mpu6050_read_byte(dev.client,GYRO_YOUT_L);
            data.gyro.y|= mpu6050_read_byte(dev.client,GYRO_YOUT_H)<<8;
            data.gyro.z = mpu6050_read_byte(dev.client,GYRO_ZOUT_L);
            data.gyro.z|= mpu6050_read_byte(dev.client,GYRO_ZOUT_H)<<8;
            printk("gyro:x %d, y:%d, z:%d\n",data.gyro.x,data.gyro.y,data.gyro.z);
            break;
        case GET_TEMP:
            data.temp = mpu6050_read_byte(dev.client,TEMP_OUT_L);
            data.temp|= mpu6050_read_byte(dev.client,TEMP_OUT_H)<<8;
            printk("temp: %d\n",data.temp);
            break;
        default:
            printk(KERN_INFO "invalid cmd");
            break;
        }
        printk("acc:x %d, y:%d, z:%d\n",data.accel.x,data.accel.y,data.accel.z);
        res = copy_to_user((void *)arg,&data,sizeof(data));
        return sizeof(data);
    }
    
    // 初始化文件系统设备操作接口
    struct file_operations fops = {
        .open = dev_open,
        .release = dev_release,
        .unlocked_ioctl = dev_ioctl, 
    };
    
    #define DEV_CNT 1
    #define DEV_MI 0
    #define DEV_MAME "mpu6050"
    
    struct class *cls;
    dev_t dev_no ;
    
    static void mpu6050_init(struct i2c_client *client)
    {
        mpu6050_write_byte(client, PWR_MGMT_1, 0x00);
        mpu6050_write_byte(client, SMPLRT_DIV, 0x07);
        mpu6050_write_byte(client, CONFIG, 0x06);
        mpu6050_write_byte(client, GYRO_CONFIG, 0x18);
        mpu6050_write_byte(client, ACCEL_CONFIG, 0x0);
    }
    static int mpu6050_probe(struct i2c_client * client, const struct i2c_device_id * id)
    {
        dev.client = client;
        printk(KERN_INFO "xj_match ok\n");
        // 初始化设备文件系统
        cdev_init(&dev.dev,&fops);    
        alloc_chrdev_region(&dev_no,DEV_MI,DEV_CNT,DEV_MAME);    
        cdev_add(&dev.dev,dev_no,DEV_CNT);
        
        // 设备初始化
        mpu6050_init(client);
    
        /*自动创建设备文件*/
        cls = class_create(THIS_MODULE,DEV_MAME);
        device_create(cls,NULL,dev_no,NULL,"%s%d",DEV_MAME,DEV_MI);
        
        printk(KERN_INFO "probe\n");
        
        return 0;
    }
    
    static int mpu6050_remove(struct i2c_client * client)
    {
        device_destroy(cls,dev_no);
        class_destroy(cls);
        unregister_chrdev_region(dev_no,DEV_CNT);
        return 0;
    }
    
    struct of_device_id mpu6050_dt_match[] = {
        {.compatible = "invensense,mpu6050"},
        {},
    };
    
    // 设备驱动注册到总线
    struct i2c_device_id mpu6050_dev_match[] = {};
    struct i2c_driver mpu6050_driver = {
        .probe = mpu6050_probe,
        .remove = mpu6050_remove,
        .driver = {
            .owner = THIS_MODULE,
            .name = "mpu6050drv",
            .of_match_table = of_match_ptr(mpu6050_dt_match), 
        },
        .id_table = mpu6050_dev_match,
    };
    module_i2c_driver(mpu6050_driver);
    MODULE_LICENSE("GPL");
    

    在代码实现中把文件系统设备实现和设备驱动实现混合在一起,个人加以分开实现,利于代码重用和设备驱动替换和多方案切换。

    验证

    通过上面的驱动, 我们可以在应用层操作设备文件从mpu6050寄存器中读取原始数据, 应用层如下

    int main(int argc, char * const argv[])
    {
        int fd = open(argv[1],O_RDWR);
        if(-1== fd){
            perror("open");
            return -1;
        }
        union mpu6050_data data = {{0}};
        while(1){
            ioctl(fd,GET_ACCEL,&data);
            printf("acc:x %d, y:%d, z:%d\n",data.accel.x,data.accel.y,data.accel.z);
            ioctl(fd,GET_GYRO,&data);
            printf("gyro:x %d, y:%d, z:%d\n",data.gyro.x,data.gyro.y,data.gyro.z);
            ioctl(fd,GET_TEMP,&data);
            printf("temp: %d\n",data.temp);
            sleep(1);
        }
        return 0;
    }
    

    最终可以获取传感器的原始数据如下
    在这里插入图片描述
    说明: 以上demo是借鉴 https://www.cnblogs.com/xiaojiang1025/p/6500540.html 的,在实际开发中个人也有实现,代码不在写该篇博客的电脑中,就借用了大神代码。

    展开全文
  • 从零开始之驱动开发linux驱动(一、驱动基础)

    万次阅读 多人点赞 2018-10-08 20:28:15
    准备19年在深圳这边找驱动相关的工作了,所以...同时该系列的驱动都是在前面新移植的linux3.16.57的基础上工作的。   之前的驱动模型学习和分析,从框架上了解的驱动的组织形式(在4.9的内核的基础上分析的)。 ...

     

    准备19年在深圳这边找驱动相关的工作了,所以从头开始再学一遍韦东山老师的驱动课程,并做好记录,希望能找到满意的工作。

    同时该系列的驱动都是在前面新移植的linux3.16.57的基础上工作的。

     

    之前的驱动模型学习和分析,从框架上了解的驱动的组织形式(在4.9的内核的基础上分析的)。

    https://blog.csdn.net/qq_16777851/article/category/7901554

     

     

    在ARM工作模式中,处理器模式切换可以通过软件控制进行切换,即修改CPSR模式位,但这是在特权模式下,当我们处于用户模式下,是没有权限实现模式转换的。若想实现模式切换,只能由另一种方法来实现,即通过中断或是异常处理过程进行切换。于是ARM指令集中提供了两条产生异常的指令,通过这两条指令可以用软件的方法实现异常,其中一个就是中断指令swi(另一个是断点中断BKPT ).

     

     软件中断指令(Software Interrupt, swi)用于产生软中断,实现从用户模式变换到管理模式,CPSR保存到管理模式的SPSR中,执行转移到swi向量。在其他模式下也可以使用SWI指令,处理器同样切换到管理模式。

    1.SWI指令格式如下:

    cond  是执行指令的条件

    immed_24  24位立即数,值为从0――16777215之间的整数

    SWI指令后面的24立即数是干什么用的呢?用户程序通过SWI指令切换到特权模式,进入软中断处理程序,但是软中断处理程序不知道用户程序到底想要做什么?SWI指令后面的24位用来做用户程序和软中断处理程序之间的接头暗号。通过该软中断立即数来区分用户不同操作,执行不同内核函数。如果用户程序调用系统调用时传递参数,根据ATPCSC语言与汇编混合编程规则将参数放入R0~R4即可。

     

    使用SWI指令时,通常使用以下两种方法进行参数传递,SWI异常处理程序可以提供相关的服务,这两种方法均是用户软件协定。SWI异常中断处理程序要通过读取引起软件中断的SWI指令,以取得24为立即数。

    1)、指令中24位的立即数指定了用户请求的服务类型,中断服务的参数通过通用寄存器传递。

    如下面这个程序产生一个中断号位12 的软中断:

    MOV R0,#34                    ;设置功能号为34
    
    SWI 12                              ;产生软中断,中断号为12

    2)、指令中的24位立即数被忽略,用户请求的服务类型有寄存器R0的值决定,参数通过其他的通用寄存器传递。

    如下面的例子通过R0传递中断号,R1传递中断的子功能号:

    MOV R0, #12                  ;设置12号软中断
    
    MOV R1, #34                  ;设置功能号为34
    
    SWI  0

     

     操作系统的主要功能是为应用程序的运行创建良好的环境,保障每个程序都可以最大化利用硬件资源,防止非法程序破坏其它应用程序执行环境,为了达到这个目的,操作系统会将硬件的操作权限交给内核程序来管理,用户程序不能随意使用硬件,使用硬件(对硬件寄存器进行读写)时要先向操作系统发出请求,操作系统内核帮助用户程序实现其操作,也就是说用户程序不会直接操作硬件,而是提供给用户程序一些具备预定功能的内核函数,通过一组称为系统调用的(system call)的接口呈现给用户,系统调用把应用程序的请求传给内核,调用相应的内核函数完成所需的处理,将处理结果返回给应用程序。

     

    操作系统里将用户程序运行在用户模式下,并且为其分配可以使用内存空间,其它内存空间不能访问,内核态运行在特权模式下,对系统所有硬件进行统一管理和控制。从前面所学知识可以了解到,用户模式下没有权限进行模式切换,这也就意味着用户程序不可能直接通过切换模式去访问硬件寄存器,如果用户程序试图访问没有权限的硬件,会产生异常。这样用户程序被限制起来,如果用户程序想要使用硬件时怎么办呢?用户程序使用硬件时,必须调用操作系统提供的API接口才可以,而操作系统API接口通过软件中断方式切换到管理模式下,实现从用户模式下进入特权模式。

    在3.16.57的内核中总共有382个系统调用

    arch/arm/kernel/colls.S
    /* 0 */		CALL(sys_restart_syscall)
    		CALL(sys_exit)
    		CALL(sys_fork)
    		CALL(sys_read)
    		CALL(sys_write)
    /* 5 */		CALL(sys_open)
    		CALL(sys_close)
    		CALL(sys_ni_syscall)		/* was sys_waitpid */
    		CALL(sys_creat)
    		CALL(sys_link)
    
            ......
    
    /* 375 */	CALL(sys_setns)
    		CALL(sys_process_vm_readv)
    		CALL(sys_process_vm_writev)
    		CALL(sys_kcmp)
    		CALL(sys_finit_module)
    /* 380 */	CALL(sys_sched_setattr)
    		CALL(sys_sched_getattr)
    		CALL(sys_renameat2)

    其中CALL的定义如下,可以看出是直接定义为代码段的某个地址了,方便数组下标索引,可以看到sys_open的偏移是5

    #define CALL(x) .long x

     

    EABI (Extended ABI)

    CONFIG_OABI_COMPAT    //表示老的系统调用接口
    CONFIG_AEABI          //新的系统调用接口

     

    EABI ,说的是这样的一种新的系统调用方式

    mov r7, #num

    swi 0x0

    原来的系统调用方式是这样,

    swi (#num | 0x900000) (0x900000是个magic值)

    也就是说原来的调用方式(Old ABI)是通过跟随在swi指令中的调用号来进行的,现在的是根据r7中的值。

    现在看两个宏,一个是

    CONFIG_OABI_COMPAT 意思是说和old ABI兼容

    另一个是

    CONFIG_AEABI 意思是说指定现在的方式为EABI

    这两个宏可以同时配置,也可以都不配,也可以配置任何一种。

    我说一下内核是怎么处理这一问题的。

    我们知道,sys_call_table 在内核中是个跳转表,这个表中存储的是一系列的函数指针,这些指针就是系统调用函数的指针,如(sys_open).系统调用是根据一个调用号(通常就是表的索引)找到实际该调用内核哪个函数,然后运行该函数完成的。

    首先,对于old ABI,内核给出的处理是给它建立一个单独的system calltable,叫sys_oabi_call_table,这样,兼容方式下就会有两个system call table, 以oldABI方式的系统调用会执行old_syscall_table表中的系统调用函数,EABI方式的系统调用会用sys_call_table中的函数指针

    配置无外乎以下4中

    第一 两个宏都配置 行为就是上面说的那样

    第二 只配置CONFIG_OABI_COMPAT , 那么以old ABI方式调用的会用sys_oabi_call_table,以EABI方式调用的 用sys_call_table,和1实质相同,只是情况1更加明确。

    第三 只配置CONFIG_AEABI 系统中不存在 sys_oabi_call_table, 对old ABI方式调用不兼容。只能 以EABI方式调用,用sys_call_table

    第四 两个都没有配置 系统默认会只允许old ABI方式,但是不存在old_syscall_table,最终会通过sys_call_table 完成函数调用

     

     

    用户空间如何让产生系统调用,即触发SWI异常

    就看定义编译内核是默认是OABI还是EABI,可以看到新的EABI是r7传参方式

    #ifndef CONFIG_CPU_THUMBONLY
    #define ARM_OK(code...)	code        //正常都是这种
    #else
    #define ARM_OK(code...)
    #endif
    
    
    	.align
    
    sigreturn_codes:
    
    	/* ARM sigreturn syscall code snippet */
    	arm_slot 0
    ARM_OK(	mov	r7, #(__NR_sigreturn - __NR_SYSCALL_BASE)	)
    ARM_OK(	swi	#(__NR_sigreturn)|(__NR_OABI_SYSCALL_BASE)	)
    
    	/* Thumb sigreturn syscall code snippet */
    	thumb_slot 0
    	movs	r7, #(__NR_sigreturn - __NR_SYSCALL_BASE)
    	swi	#0
    
    	/* ARM sigreturn_rt syscall code snippet */
    	arm_slot 1
    ARM_OK(	mov	r7, #(__NR_rt_sigreturn - __NR_SYSCALL_BASE)	)
    ARM_OK(	swi	#(__NR_rt_sigreturn)|(__NR_OABI_SYSCALL_BASE)	)
    
    	/* Thumb sigreturn_rt syscall code snippet */
    	thumb_slot 1
    	movs	r7, #(__NR_rt_sigreturn - __NR_SYSCALL_BASE)
    	swi	#0

     

     

     

    通过上面的calls.S和下面两句可以知道,sys_call_table就是系统调用表的首地址。而#include "call.S"里面的内容在前面已经说明了,

    就是以 .long sys_xxx   的函数的地址

    	.type	sys_call_table, #object
    ENTRY(sys_call_table)
    #include "calls.S"

    方然也有老的调用接口,但对里面的系统调用函数,明显都是一样的

    	.type	sys_oabi_call_table, #object
    ENTRY(sys_oabi_call_table)
    #include "calls.S"

     

     

    下面就是SWI异常处理函数的实现

    
    /*=============================================================================
     * SWI handler
     *-----------------------------------------------------------------------------
     */
    
    	.align	5
    ENTRY(vector_swi)
    #ifdef CONFIG_CPU_V7M
    	v7m_exception_entry
    #else
    	sub	sp, sp, #S_FRAME_SIZE
    	stmia	sp, {r0 - r12}			@ Calling r0 - r12
     ARM(	add	r8, sp, #S_PC		)
     ARM(	stmdb	r8, {sp, lr}^		)	@ Calling sp, lr
     THUMB(	mov	r8, sp			)
     THUMB(	store_user_sp_lr r8, r10, S_SP	)	@ calling sp, lr
    	mrs	r8, spsr			@ called from non-FIQ mode, so ok.
    	str	lr, [sp, #S_PC]			@ Save calling PC
    	str	r8, [sp, #S_PSR]		@ Save CPSR
    	str	r0, [sp, #S_OLD_R0]		@ Save OLD_R0
    #endif
    	zero_fp
    	alignment_trap ip, __cr_alignment
    	enable_irq
    	ct_user_exit
    	get_thread_info tsk
    
    	/*
    	 * Get the system call number.
    	 */
    
    #if defined(CONFIG_OABI_COMPAT)
    
    	/*
    	 * If we have CONFIG_OABI_COMPAT then we need to look at the swi
    	 * value to determine if it is an EABI or an old ABI call.
    	 */
    #ifdef CONFIG_ARM_THUMB
    	tst	r8, #PSR_T_BIT
    	movne	r10, #0				@ no thumb OABI emulation
     USER(	ldreq	r10, [lr, #-4]		)	@ get SWI instruction
    #else
     USER(	ldr	r10, [lr, #-4]		)	@ get SWI instruction
    #endif
     ARM_BE8(rev	r10, r10)			@ little endian instruction
    
    #elif defined(CONFIG_AEABI)
    
    	/*
    	 * Pure EABI user space always put syscall number into scno (r7).
    	 */
    #elif defined(CONFIG_ARM_THUMB)
    	/* Legacy ABI only, possibly thumb mode. */
    	tst	r8, #PSR_T_BIT			@ this is SPSR from save_user_regs
    	addne	scno, r7, #__NR_SYSCALL_BASE	@ put OS number in
     USER(	ldreq	scno, [lr, #-4]		)
    
    #else
    	/* Legacy ABI only. */
     USER(	ldr	scno, [lr, #-4]		)	@ get SWI instruction
    #endif
    
    	adr	tbl, sys_call_table		@ load syscall table pointer
    
    #if defined(CONFIG_OABI_COMPAT)
    	/*
    	 * If the swi argument is zero, this is an EABI call and we do nothing.
    	 *
    	 * If this is an old ABI call, get the syscall number into scno and
    	 * get the old ABI syscall table address.
    	 */
    	bics	r10, r10, #0xff000000
    	eorne	scno, r10, #__NR_OABI_SYSCALL_BASE
    	ldrne	tbl, =sys_oabi_call_table
    #elif !defined(CONFIG_AEABI)
    	bic	scno, scno, #0xff000000		@ mask off SWI op-code
    	eor	scno, scno, #__NR_SYSCALL_BASE	@ check OS number
    #endif
    
    local_restart:
    	ldr	r10, [tsk, #TI_FLAGS]		@ check for syscall tracing
    	stmdb	sp!, {r4, r5}			@ push fifth and sixth args
    
    	tst	r10, #_TIF_SYSCALL_WORK		@ are we tracing syscalls?
    	bne	__sys_trace
    
    	cmp	scno, #NR_syscalls		@ check upper syscall limit
    	adr	lr, BSYM(ret_fast_syscall)	@ return address
    	ldrcc	pc, [tbl, scno, lsl #2]		@ call sys_* routine
    
    	add	r1, sp, #S_OFF
    2:	cmp	scno, #(__ARM_NR_BASE - __NR_SYSCALL_BASE)
    	eor	r0, scno, #__NR_SYSCALL_BASE	@ put OS number back
    	bcs	arm_syscall
    	mov	why, #0				@ no longer a real syscall
    	b	sys_ni_syscall			@ not private func
    
    #if defined(CONFIG_OABI_COMPAT) || !defined(CONFIG_AEABI)
    	/*
    	 * We failed to handle a fault trying to access the page
    	 * containing the swi instruction, but we're not really in a
    	 * position to return -EFAULT. Instead, return back to the
    	 * instruction and re-enter the user fault handling path trying
    	 * to page it in. This will likely result in sending SEGV to the
    	 * current task.
    	 */
    9001:
    	sub	lr, lr, #4
    	str	lr, [sp, #S_PC]
    	b	ret_fast_syscall
    #endif
    ENDPROC(vector_swi)
    

     

    真正的系统调用则是用过下面这个函数实现的。

    asmlinkage long sys_open(const char __user *filename,
    				int flags, umode_t mode);
    
    
    SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
    {
    	if (force_o_largefile())
    		flags |= O_LARGEFILE;
    
    	return do_sys_open(AT_FDCWD, filename, flags, mode);
    }
    
    
    
    SYSCALL_DEFINE3是一个宏,可以自己解析一下,实际下面的两个是一样的
    asmlinkage long sys_open(const char __user *filename,int flags, umode_t mode);
    SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode);

     

     

     

    对照上图,可以看到应用程序使用系统调用接口,需要从用户级切换到内核级。

    方法是:通过SWI指令和寄存器传入参数,通过输入的swi中断号,直接以查表方式找到对应的系统调用函数。

    以open一个led灯为例:

    可以看到调用顺序依次是应用程序 open ->  swi软中断 -> 系统调用接口  -> sys_open  ->

    文件子系统VFS中的open -> 驱动程序open  -> 硬件操作

     

    其中应用程序空间的open需要通过swi指令来实现swi中断。

    通过文件的属性(普通文件,设备文件),来不同的处理。

    如果是设备文件,则继续通过属性查看是字符还是块设备文件,找到对应的驱动程序,最终操纵硬件。

     

    展开全文
  • linux驱动(一):linux驱动框架

    万次阅读 多人点赞 2018-01-10 14:42:13
    编写linux驱动先看一下驱动框架是什么样子的。 驱动编写和应用层编写有什么区别呢? (一)首先 入口函数的问题。应用层编写我们的入口就是main函数,但在驱动编写时不是这样的,有两种情况, 1、缺省情况下 int...
  • [一] 、Linux驱动 1.软件驱动 驱动硬件,使硬件处于某种工作模式,提供控制硬件方法 2.驱动的地位 驱动是连接内核与设备的桥梁 [二]、设备分类 1.字符设备...
  • linux驱动开发的经典书籍

    千次阅读 2016-11-07 14:55:07
    Linux驱动学习的最大困惑在于书籍的缺乏,市面上最常见的书为《linux_device_driver 3rd Edition》,这是一本很经典的书,无奈Linux的东东还是过于庞大,这本侧重于实战的书籍也只能停留在基本的接口介绍上,更深入...
  • linux2.6驱动开发系列教程

    万次阅读 热门讨论 2011-11-01 21:44:33
    这段时间一直在做android下的驱动,android驱动底层跟linux如出一辙,所以这里准备做一个专题,把linux驱动做一个总结,为android接下来的驱动开发打好基础,大致的思想如下: 一、linux驱动基础开发 0、linux驱动...
  • Linux设备驱动开发入门

    万人学习 2018-10-22 21:38:04
    本课程讲解Linux驱动程序开发基本知识,程序架构,字符设备编程,杂项设备编程,具体硬件模块驱动开发
  • 于此,将框架(Framework)和设计模式(Design Pattern)应用于Linux驱动开发,说明了如何以面向对象、设计模式和框架概念来看待Linux驱动程序的架构。其直接的益处就是:让我们能基于一致的设计理念来结合Android HAL与...
  • Linux设备驱动开发详解:基于最新的Linux 4.0内核》
  • 包括3个大项目和若干衍生项目,涵盖:数码相框、电子书、指针、链表、Makefile、网络编程Socket、USB摄像头、CMOS摄像头、视频监控、WIFI、3G网卡、...总体格调:应用为主驱动为辅,手把手现场写代码,再现项目实现过程
  • 本课程是linux驱动开发的第9个课程,主要内容是linux的I2C子系统以及电容触摸屏驱动的移植。学习本课程的核心点在于I2C子系统,目标是对I2C驱动框架的彻底理解和熟练运用。本课程承袭前面platform平台总线的讲解思路...
  • ARM-Linux驱动开发

    千次阅读 多人点赞 2012-01-17 23:23:03
    以下是原创作品,转载请标明出处...通过IO端口读取外部数据,带中断s3c2410_gpio_cfgpin函数解析控制IO端口 s3c2410_gpio_setpin()的使用Linux 驱动--ADC驱动ARM-Linux驱动--ADC驱动(中断方式)S3C2440上touc
  • Android驱动深度开发视频教程

    万人学习 2018-11-30 10:35:12
    也许是中国第一个讲解android驱动的课程,涵盖: bootloader,内核移植,INIT进程,框架(BINDER IPC,SERVICE FRAMEWORK Activity Manager Serive,JNI,HAL等),binder驱动,logger,Ashmen,电源管理,常用驱动(如灯光...
  • 嵌入式Linux驱动教程(韦东山2期)

    万人学习 2018-11-30 11:01:24
    1.没有废话,句句都是干货!学习后保证可以跟着视频完成相应的实验。 2.现场从0编写/调试工作中的绝大部分驱动,内容--- 理论 +
  • Linux从未停歇脚步。Linus Torvalds,世界上最伟大的程序员之一,Linux内核的创始人,Git的缔造者,仍然在没日没夜的合并补丁,升级内核。做技术,从来没有终南捷径,拼的就是坐冷板凳的傻劲。 这是一个连阅读都被...
  • 本博实时更新《Linux设备驱动开发详解(第3版)》(即《Linux设备驱动开发详解:基于最新的Linux 4.0内核》)的最新进展。 目前已经完成稿件。 2015年8月9日,china-pub开始上线预售: ... 2015年8月20日,各路朋友报喜...
  • 嵌入式Linux驱动开发实战视频教程

    千次阅读 2014-10-13 10:00:40
    嵌入式Linux驱动开发实战教程(内核驱动、看门狗技术、触摸屏、视频采集系统) 适合人群:高级 课时数量:109课时 用到技术:嵌入式 Linux 涉及项目:驱动开发、看门狗技术、触摸屏、视频采集 咨询qq:1840215592...
  • 近日稍微对Android中的驱动开发做了一些简要的了解,稍稍理清了一下Android驱动开发的套路,总结一下笔记。HAL:Hardware Abstract Layer 硬件抽象层,由于Linux Kernel需要遵循GPL开源协议,硬件厂商为了保护自己硬件...
1 2 3 4 5 ... 20
收藏数 188,240
精华内容 75,296
关键字:

linux驱动开发