驱动移植学习心得

2023-10-29

系统移植:

    把操作系统(Linux)能够在芯片(板子)上运行
    目标:在开发板上运行操作系统

    嵌入式系统:(linux)
        以应用为中心,把软硬件进行裁剪,适用于应用的专用计算机系统

    
    1、交叉编译环境搭建

        开发主机:
            编译工具,针对开发板的编译工具(arm-gcc)
            arm-none-linux-gnueabi-gcc

            传输环境(网络):
                tftp:上传下载服务器
                    sudo apt install tftpd-hpa 下载安装tftp
                    
                    主机提供哪个目录用于上传下载
                    sudo vim /etc/default/tftpd-hpa(配置文件)

                        # /etc/default/tftpd-hpa

                        TFTP_USERNAME="tftp"
                        TFTP_DIRECTORY="/tftpdir" #配置上传下载的目录
                        TFTP_ADDRESS="0.0.0.0:69"
                        TFTP_OPTIONS="-l -c -s"
                                              在对应的目录(根目录)下创建tftpdir
                            sudo    mkdir    /tftpdir
                            sudo    chmod    0777    /tftpdir
                
                nfs:文件共享服务器
                    
                    sudo apt install nfs-kernel-server 下载安装nfs
            
                    主机提供哪个目录用于共享文件
                    sudo vim /etc/exports
                        /nfsdir/rootfs     *(rw,sync,no_subtree_check,no_root_squash)
                        表示共享/nfsdir/rootfs

    2、系统移植----linux
        把别人写好的系统代码进行修改,放在自己的板子上可以运行
        
        Linux系统的启动流程:
            电源键----->引导程序(执行硬件自检,加载操作系统)------>加载系统(启动系统)------>加载文件系统(执行程序)

        系统移植:
        
            1、引导程序
            2、系统(内核):linux
            3、文件系统

------------------------
1、bootloader移植

    bootloader:是硬件启动的引导程序,是加载操作系统的前提
    在操作系统内核运行前的一小段代码,对软硬件进行相应的初始化和设置,为执行操作系统做准备,然后加载操作系统
        
    bootloader不属于操作系统;需要依赖cpu的体系结构与板载硬件

    bootloader的操作模式:
        a、自启动模式:直接进行初始化化,加载内核(操作系统)到内存中运行
        
        b、交互模式:留在bootloader执行,通过串口进行通信,然后完成控制

    bootloader基本功能:
        初始化硬件
        bootloader自搬移
        执行用户的命令
        加载内核
        

    bootloader的启动:
        当芯片上电、复位后,所有cpu会自动从某个地址进行执行,只用把bootloader放在对应位置

    常见的bootloader:

        U-boot
        LILO:linux磁盘引导程序
        GRUB:GNU的LILO
        LinuxBIOS
        RedBoot

        
    U-boot:通用引导程序,使用广,兼容性强
    U-boot特点:
        1、代码结构清晰、易于移植
        2、支持多种处理器体系结构
        3、支持众多开发板
        4、命令丰富
        5、支持网络协议、USB、SD等设备协议
        6、支持文件系统
        7、用户多,出现问题可以及时搜索解决

    u-boot命令:
        命令类型:环境变量、数据传输、存储器访问、加载运行
        printenv:pri----打印所有环境变量
        setenv:set------设置环境变量值
        saveenv:save---保存环境变量值
    
    
        bootdelay:进入自启动模式的倒数计时
        gatewayip:网关地址
        ipaddr:设备网络地址----板子
        netmask:子网掩码
        serverip:服务端地址---linux虚拟机
        bootcmd:进入自启动模式后,uboot要执行的命令
        bootargs:当加载操作系统时传递给操作系统的参数

        loadb    地址
        tftp:把网络中tftp服务端(serverip)文件下载某个地址

        go xxxx:跳转到地址执行 pc = xxx

        movi:
        movi init----初始化emmc(flash),并显示信息
        movi read
        movi write

        bootm:系统引导指令

        
sudo apt install u-boot-tools
sudo apt install libncurses5-dev

sudo dpkg --add-architecture i386
sudo wget -nc -O /usr/share/keyrings/winehq-archive.key https://dl.winehq.org/wine-builds/winehq.key
sudo wget -NP /etc/apt/sources.list.d/ https://dl.winehq.org/wine-builds/ubuntu/dists/focal/winehq-focal.sources
sudo apt update
sudo apt install --install-recommends winehq-stable

系统移植:
    能够在开发板(硬件)运行Linux系统,把Linux系统移植到特定硬件进行运行

     系统移植:

    1、引导程序
    2、系统内核
    3、文件系统

----------------------------------------
引导程序:bootloader

    bootloader:是硬件启动的引导程序,是操作系统执行的前提,是系统内核运行前的一段代码,完成特定硬件初始化和设定

    移植bootloader:适配于当前硬件的引导程序,能够在当前硬件上引导启动操作系统

    bootloader:u-boot-----通用引导程序
    
    u-boot命令:
        pri:查看环境变量
        set:设置环境变量
        
        loadb
        tftp
        
        ping
        
        go
        bootm


u-boot移植:移植一个适配当前硬件的引导程序
    
    在u-boot目录中包含了引导程序的所有代码,只要能够编译出对应的可执行程序----引导程序

    u-boot支持众多cpu,所以在代码中包含所有cpu的引导功能,选择硬件对应的cpu进行编译
    移植:在u-boot源码中指定我们需要的功能,需要的外设硬件,需要的cpu编译为适配于当前硬件的引导程序

    平台相关:
        arch:体系结构相关的代码--cpu
        board:芯片厂商的板载开发文件,生产初始的硬件设备对应的引导流程代码(厂商板子配置的引导代码)
        include:头文件
    平台无关:
        common:命令
        drivers:驱动,硬件控制
        net:网络
    文档:
        doc
    工具:
        tools

    
    移植步骤:
    0、选择对应的编译工具
        修改Makefile,指定编译工具

    1、查看当前芯片,u-boot找到最相近的硬件设备
        指定产品cpu,我们所使用的是三星exynos4412(armv7)
            查看arch/arm/cpu目录,进行搜索确定当前的u-boot源码支持我们的cpu
            u-boot支持当前cpu

        查找board最相近硬件设备,根据相近的板子配置来完成我们的配置
            指定产品 BOARD,三星已经对exynos4412芯片设置了初始的硬件设备,完成了初始化的程序
            相近:board/samsung/origen

        参考对应board的配置
            复制一份origen的配置,改为自己的配置(引导程序要完成的操作,代码)
            boards.cfg:所有板子的描述说明
                描述所支持的开发板和相应的信息,后续的编译过程(编译为引导程序)会根据配置名来检索到对应的信息
        make fs4412_config
        make
            u-boot.bin---引导程序

    2、添加三星的加密方式(三星才添加)
            三星芯片:加密,把核心代码拿出
        需要单独添加
        exynos需要三星的初始引导加密,我们的u-boot才能执行
        需要使用到三星提供的加密工具,目的就是根据三星的启动要求对u-boot.bin进行处理

            sdfuse_q、CodeSign4SecureBoot、build.sh---->u-boot2013
        
        修改Makefile:
            $(obj)u-boot.bin:    $(obj)u-boot
                $(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
                $(BOARD_SIZE_CHECK)
                添加:
                @#./mkuboot
                    @split -b 14336 u-boot.bin bl2
                @+make -C sdfuse_q/
                    @#cp u-boot.bin u-boot-4212.bin
                    @#cp u-boot.bin u-boot-4412.bin
                @#./sdfuse_q/add_sign
                    @./sdfuse_q/chksum
                    @./sdfuse_q/add_padding
                    @rm bl2a*
                    @echo
        
        要把三星核心文件添加到u-boot.bin中

uboot移植:
    
    启动代码:引导内核(操作系统)的代码

    uboot启动流程:
        
        阶段一:
            cpu初始化
                svc
                关闭mmu
            基本硬件初始化
                关闭中断、关闭看门狗;初始化时钟、串口、内存
    
            自搬移到内存执行
            设置堆栈
        阶段二:
            board_init_r:
                board_init_f//大部分硬件初始化
                主循环
                for(;;)
                {
                    main_loop();//进入交互模式(readline,run_command) 或 自启动模式 (bootdelay  读取bootcmd执行自启动)
                }


    移植:
    1、下载解压uboot源码,进行移植
        tar xvf u-boot-2013.01.tar.bz2

    2、查看板子芯片cpu,找最相近的板子作为参考
        cpu:exynos
        2013uboot支持当前cpu

        查找相近板子作为参考进行移植(board目录就是厂商提供的参考板的配置代码)
        samsung/origen
        a、用参考配置,生成当前硬件的配置fs4412目录
         cp board/samsung/origen/ board/samsung/fs4412 -rf

        b、修改fs4412目录变成自己硬件配置(配置代码)
            文件名等修改
            
            完成自己的配置(实现配置代码):引导程序需要有什么功能

    3、编译生成
        生成uboot程序,有引导程序
        
        a、完成make的配置,让makefile知道怎么去编译
            boards.cfg:编译配置说明
            参考已有的配置说明,添加新的说明

        三星(额外):
            sdfuse_d目录、CodeSign4SecureBoot目录、build.sh
            ./build.sh                
    
        b、make编译
            生成 u-boot.bin

        u-boot-fs4412.bin:uboot程序,引导程序

    4、烧写
        烧写到sd卡
            查看文档

        烧写到板子上emmc----固化到芯片上
            a、先运行一个有emmc功能的uboot
            movi init----有emmc功能
                FS4412[general] # movi init
                Device: S5P_MSHC4
                Manufacturer ID: 15
                OEM: 100
                Name: 4FPD3
                Tran Speed: 0
                Rd Block Len: 512
                MMC version 4.0
                High Capacity: Yes
                Capacity: 7.3 MiB
                Bus Width: 2-bit

            b、运行下载,把要固化的uboot下载到板子的内存中

                保证电脑与板子能够ping通
                板子设置serverip(电脑ip)、gatewayip、ipaddr
                ping 电脑ip

                tftp    内存地址        文件名
                    板子上的内存    电脑上tftp服务端文件夹中的文件
                tftp    41000000    u-boot-fs4412-general-2013.bin
                    错误:
                    Loading: *
                    TFTP error: 'Permission denied' (0)
                    Starting again
                    把要下载的文件的权限提升

            c、把板子内存上的uboot烧录到emmc中的u-boot分区
                movi    write    u-boot(emmc中uboot分区)    41000000
        
        uboot烧写到emmc(板子上):
    

        以sd卡启动uboot
        使用tftp下载:
            tftp    文件下载的内存地址        服务端的文件
            tftp 41000000 u-boot-fs4412.bin
        
        烧写到emmc中:
            movi    write    写入的位置    写入的内容的当前存放的地址
            movi write u-boot(emmc中的u-boot分区中) 41000000
                

    5、启动uboot

        把拨码开关 1000----sd卡启动
              1234

        把拨码开关 0110----emmc启动
              1234

    6、实现当前硬件配置(配置代码)
        移植内容:对当前硬件裁剪内容(添加、删除功能代码),启动的代码中进行添加

        a、led
        b、uart
        c、net
        d、emmc
    

    通过网络烧写设置:
        1、把虚拟机改为桥接模式
            192.168.3.xxxx
        2、关闭虚拟机网络托管服务
            sudo /etc/i
nit.d/network-manager stop

        3、修改板子的ip地址
            serverip = 虚拟机的ip地址
            ipaddr = 设置板子的ip地址
            gatewayip = 网关 192.168.x.1
            

------------------------------------------------------------
系统移植:

    Linux系统与Linux内核
    内核:提供了硬件的处理(硬件抽象层)、磁盘和文件系统的控制、任务处理(进程管理、内存管理等)系统的基础运行部分
    
    系统:包含了Linux内核、工具集、各种库、图形界面、桌面管理器、各种应用程序为一体的系统

    系统移植:Linux内核,能够运行Linux核心组件

    Linux内核组件:
        
        进程管理
        内存管理
        文件系统
        网络系统
        设备管理

    移植Linux内核:
        根据Linux内核源码,通过添加、删除变为能够适配于当前硬件Linux内核
    
    1、下载解压Linux内核源码
        
        在3.0版本Linux内核前,没有设备树
        在3.0版本Linux内核后,有设备树

        arch:cpu相关的代码(体系结构相关代码)

        Makefile:编译控制
            把内核代码编译为内核程序
        .config:编译配置的存放脚本

    2、查看芯片,查找对应厂商与当前硬件最相近的板子的Linux内核配置(Linux内核移植配置)

        先查看芯片:
            exynos
        查找对应三星厂商exynos芯片初始板的配置

            所有厂商芯片的初始板的配置:

            exynos芯片(三星厂商的默认配置):
            arch/arm/config/exynos_defconfig-----芯片配置(内核要有什么功能)--生成内核镜像
            外设硬件信息说明(三星厂商的默认硬件配置)板子:
            arch/arm/boot/dts/exynos4412-origen.dts
            

        选择配置:要编译内核的配置(哪些内容要加入到内核)
        make    exynos_defconfig
        
    3、编译内核
        生成内核镜像

        make uImage
            uImage专门给uboot进行启动引导的内核


        uImage缺少当前硬件的硬件信息

        生成设备树:设备树就是描述硬件信息文件
        make dtbs
            exynos4412-origen.dtb

系统移植:内核移植

    Linux内核组件:
        
        进程管理
        内存管理
        文件系统
        网络管理
        设备管理

    移植内核:-----操作系统
        多任务
        多用户

    1、下载解压linux内核
        tar xvf linxu-3.14.tar.xz

        Linux内核以不同的文件夹把不同的功能操作分开

        每个目录就是对应的功能(代码)

        最终当前硬件需要什么功能就移植什么功能
        
    2、查看cpu,查找相近板子进行移植

        内核
        设备树

        内核:查找芯片对应的芯片,使用对应的芯片的内核配置作为参考

            linux-3.14/arch/arm/configs/exynos_defconfig
            //linux-3.14/arch/arm/configs/exynos_fs4412_defconfig
            内核编译配置选择------内核功能选择

            make    exynos_defconfig:使用参考内核配置作为当前内核编译的内核配置,生成.config文件
                .config:内核编译配置文件
            make    uImage
            生成内核镜像文件,内核文件
                Image arch/arm/boot/uImage is ready

        设备树:查找厂商的默认初始硬件配置文件(设备树文件),使用厂商的设备树配置作为参考

            linux-3.14/arch/arm/boot/dts/exynos4412-origen.dts

            拷贝出新的设备树文件:作为设备描述
            cp  linux-3.14/arch/arm/boot/dts/exynos4412-origen.dts  linux-3.14/arch/arm/boot/dts/exynos4412-fs4412.dts
            fs4412.dts就是当前硬件使用参考配置
            编译时需要添加这个设备树要进行编译
            vim linux-3.14/arch/arm/boot/dts/Makefile
            在对应位置添加一个编译:表示要编译新的设备树文件
            dtb-$(CONFIG_ARCH_EXYNOS) += exynos4210-origen.dtb \
                        exynos4412-fs4412.dtb \
            
            编译设备树
            make dtbs
                arch/arm/boot/dts/exynos4412-fs4412.dtb

        make    exynos_defconfig
        make    uImage---内核镜像
        
        make    dtbs------设备树文件


    3、uboot引导内核

        网络下载引导:从主机获取对应uImage和exynos4412-fs4412.dtb,uboot加载内核
            交互模式:
            1、在uboot中通过网络下载的方式,把内核和设备树下载到硬件的内存中
                uImage和exynos4412-fs4412.dtb需要在电脑上tftp下载目录中

                tftp 41000000 uImage
                tftp 42000000 exynos4412-fs4412.dtb

            2、uboot从内存中引导内核执行
                bootm 内核内存地址        文件系统内存地址    设备树内存地址
                bootm 41000000 - 42000000

            自启动模式
                倒数计时结束后进入自启动模式
                自启动模式执行环境变量为 : bootcmd 内容
                执行bootmcd环境变量
                
                设置环境变量:bootcmd---进行下载启动内核

                set bootcmd xxxxxxx;
                save

        uboot引导内核,启动内核时,需要传递参数给内核,内核根据参数进行启动
            bootargs:存储传递给内核的参数,内核根据bootargs作为启动执行参数

            set bootargs root=/dev/nfs nfsroot=192.168.3.31:/nfsdir/rootfs rw console=ttySAC2,115200  init=/linuxrc  ip=192.168.3.20
            save

            root=/dev/nfs   :文件系统
            nfsroot=192.168.3.31:/nfsdir/rootfs   :nfs网络文件系统位置
            rw :读写
            console=ttySAC2,115200 :内核启动信息显示位置
            init=/linuxrc :指定内核启动后执行的第一个进程(文件系统中)
            ip=192.168.3.198:内核系统的ip地址
            
    4、修改内核,适配与当前硬件

        默认配置和当前硬件---网卡
        

        修改内核配置添加网卡
        在内核中移植网络设备:
        内核
            1、为了硬件能够工作:
            修改文件driver/clk/clk.c
            修改
            static bool clk_ignore_unused;
            为
            static bool clk_ignore_unused = true;

            2、修改内核具备对应功能----把对应网卡功能编译进内核中

            配置内核:--菜单配置,让内核添加删除功能
            make    menuconfig
                读取.config文件,修改.config        
                
                network
                网络协议:支持什么网络
                driver
                网卡配置:dm9000网卡功能
            
                由于现在不会把文件系统固化到板子上,把电脑上的一个目录(文件夹)作为板子的文件系统
                板子运行内核后,内核要运行文件系统执行程序
                板子内核要把电脑上的目录作为文件系统
                
                即:
                板子运行内核从网络上挂载文件系统(通过网络的方式拿给内核运行)
                    nfs:共享文件夹
                    电脑通过nfs共享一个目录,板子就把电脑通过nfs共享的目录作为文件系统
                
                板子文件系统在网络上通过nfs提供
                file system
                文件系统设置:支持网络文件系统

        make uImage
            
        设备树
        在设备树中添加网卡设备的描述(添加网卡信息)
        vim  arch/arm/boot/dts/exynos4412-fs4412.dts
        网卡描述
        srom-cs1@5000000 {    //bank1 物理设备 描述
        compatible = "simple-bus";
        #address-cells = <1>;
        #size-cells = <1>;
        reg = <0x5000000 0x1000000>;
        ranges;

        ethernet@5000000 {
            compatible = "davicom,dm9000";
            reg = <0x5000000 0x2         0x5000004 0x2>;
            interrupt-parent = <&gpx0>;
            interrupts = <6 4>;
            davicom,no-eeprom;
            mac-address = [00 0a 2d a6 55 a2];
            };
        };
        make dtbs

    5、make uImage过程

        根据arch/arm/kernel/vmlinux.lds----->源码路径下生成vmlinux内核镜像(无压缩内核)------->Image------->二次压缩------>zImage------->mkimage(工具)---->uImage(uboot引导专用的内核镜像)

        
    6、kernel启动流程
        
        1、第一阶段:汇编阶段
        arch/arm/boot/compressed/head.S
            完成自解压

        arch/arm/kernel/head.S
            cpu自检
                检查自己的合法性
                cpu类型、机器类型
            bl      __vet_atags    检查bootargs,验证uboot传入的参数以及获取设备树

            bl      __create_page_tables    开启内存mmu 页表
                         为后面开启mmu(内存管理单元)做准备
            b       __enable_mmu    开启mmu
    
        arch/arm/kernel/head-common.S
            把数据段    搬移到虚拟内存
            
            b       start_kernel

        2、第二阶段:C语言阶段
        init/main.c
            start_kernel(void)
                进行各种设备   功能的初始化
                
                rest_init();
                    开启内核多线程
                    kernel_thread();
                        kernel_init:内核线程,开启第一个用户进程
                    根文件系统挂载、运行用户进程
            
                

    电脑要支持nfs V2
        sudo cat /proc/fs/nfsd/versions
        在/etc/default/nfs-kernel-server  最后位置新建一行添加
        sudo vim  /etc/default/nfs-kernel-server
            RPCNFSDOPTS="--nfs-version 2,3,4 --debug --syslog"
        
        重启nfs
        sudo service nfs-kernel-server restart


移植文件系统:

    1、使用busybox-1.22.1.tar.bz2工具来创建目录,以及命令

        下载解压busybox工具
        
        make menuconfig配置

        make
    
        生成目录与命令:
        make install
            在./_install目录,中存放生成的目录与命令

        根据文件系统中应该有哪些目录进行创建新目录
        bin  dev  etc  linuxrc  mnt  proc  root  sbin  sys  tmp  usr  var

        需要在文件系统中执行程序,文件系统中需要包含运行库
            添加运行库        
        根据手册实现
        
        把工具做好的文件系统_install 拷贝到nfs中为rootfs
        sudo cp -rf  _install/ /nfsdir/rootfs

           

驱动开发:

在操作系统的基础上实现驱动程序

1、驱动开发的环境
    系统在网络中
    1、ubuntu中配置环境

        内核实现、文件系统实现、驱动实现---ubuntu中实现

        gcc-4.6.4----->arm-none-linux-gnueabi-gcc

        a、通过tftp提供uImage和.dtb----提供内核

        b、通过nfs提供文件系统rootfs

    2、板子运行环境配置

        1、板子上运行uboot引导程序

        2、配置uboot环境变量
            ipaddr
            serverip
            gatewayip
                支持网络--uboot
            bootcmd----uboot自启动模式自动执行

            bootcmd tftp 41000000 uImage \; tftp 42000000 xxx.dtb\; bootm 41000000 - 42000000
            bootargs----uboot提供给操作系统内核,内核知道怎么执行----执行bootm时
            
            bootargs /dev/nfs rootfs=xxxxx:/xxxxx/rootfs  rw console=ttySAC2,115200 init=/linuxrc ip=xxxxxx
                        
        3、uboot通过tftp下载内核和设备树
            tftp    内存地址    uImage
            tftp    内存地址    .dtb

        4、uboot运行启动内核
            bootm    内核地址    -    设备树地址


    需要有内核和文件系统:

    a、内核编译
        步骤:
        1、设置交叉编译环境
            vim    Makefile

            ARCH            ?= arm
            CROSS_COMPILE   ?= /home/ubuntu/tools/gcc-4.6.4/bin/arm-none-linux-gnueabi-

        2、选择soc,找到当前硬件对应的soc芯片,必须挑选适配于当前硬件
            make    exynos_defconfig
                生成.config------编译配置(哪些要编译,哪些不编译,内核有哪些功能)

        3、针对默认的soc芯片进行裁剪适配于当前硬件
            make    menuconfig

        4、编译内核
            make    uImage

        5、编译设备树:描述设备信息
            找最相近的外设设备树,进行修改变成当前硬件设备的设备信息描述
            以默认的dts作为参考,变为我们自己的dts

            cp linux-3.14/arch/arm/boot/dts/exynos4412-origen.dts linux-3.14/arch/arm/boot/dts/exynos4412-fs4412.dts
            vim linux-3.14/arch/arm/boot/dts/Makefile---添加编译
    
        6、编译设备树
            make dtbs

2、驱动开发:

    编写驱动代码
    1、用什么工具写代码
        vim(任何编辑器)
        
        查看代码工具(我们要知道Linux内核的函数原型(定义),才知道如何调用)
        source insight---查看代码工具

        安装source insight工具
            根据文档提示安装软件
            打开软件提示错误
                https://blog.csdn.net/zanda_/article/details/82916744
        要使用source insight工具用作查看内核代码:

        在windows的文件夹下解压linux内核代码

        1、设置新工程用作查看
            project--->new project
            在第一个对话框中,第一个文本框(行编辑器),输入工程的名字
            
            在第二个对话框中,第一个本文框中选择刚解压的Linux内核源码目录(顶层linux-3.14),点击ok

            在第三个对话框中,在对话框中选择要查看的目录/文件
                需要选择的目录文件:
                include
                init
                kernel
                arch/arm/kernel
                arch/arm/include/asm
                driver/base
                driver/char
                driver/i2c
                driver/spi
                fs/char_dev.c
            点击close关闭

            重新选择project---->open project
                选择刚才创建的工程名
                ok
                如果提示同步,则选择确认进行同步
            
        
    2、写驱动

        vim
        记事本
        
        使用source insight编写驱动代码
        
        只要是驱动程序,就必须满足驱动框架,不然是没办法编译为驱动程序

        驱动代码必须有4部分:
        //1、头文件
        #include <linux/init.h>
        #include <linux/module.h>

        //2、驱动入口函数的声明,在内核加载驱动时,执行哪个函数;在内核卸载驱动时,执行哪个函数
        //声明:加载时的入口声明
        module_init(hello_init);
        //声明:卸载时的入口声明
        module_exit(hello_exit);

        //3、加载函数、卸载函数的实现
        //加载函数的实现:当内核加载驱动(内核执行这个驱动时,就会调用的函数)
        static int __init hello_init(void)
        {

            return 0;
        }

        //卸载函数的实现:当内核卸载驱动(内核删除这个驱动时,就会调用的函数)
        static void __exit hello_exit(void)
        {

        }

        //4、协议选择GPL
        MODULE_LICENSE("GPL");
            
        
        Linux内核如何使用模块驱动:
        
        加载:
        insmod    驱动程序路径.ko
        
        查看:
        lsmod:查看当前已经加载的驱动模块        


        卸载:
        rmmod    驱动程序名
        [root@farsight ]# rmmod 1st_drv
            rmmod: can't change directory to '/lib/modules': No such file or directory
        [root@farsight ]# mkdir /lib/modules--创建目录
        [root@farsight ]# rmmod 1st_drv
            rmmod: can't change directory to '3.14.0': No such file or directory
        [root@farsight ]# mkdir /lib/modules/3.14.0--创建目录
        [root@farsight ]# rmmod 1st_drv
        [  118.015000] hello exit func
            

            

驱动框架:
四部分组成
1、头文件部分
#include <linux/init.h>
#include <linux/module.h>
2、驱动的入口声明:
加载时的入口声明:当内核加载(安装)驱动时,执行的内容(函数)的声明
module_init(函数名);
卸载时的入口声明:当内核卸载(删除)驱动时,执行的函数
module_exit(函数名);

3、入口函数的实现
//加载函数
static int __init hello_init(void)
{
    //加载驱动时执行
    return 0;
}
//卸载函数
static void __exit hello_exit(void)
{
    //卸载驱动时执行
}

//4、GPL协议
MODULE_LICENSE("GPL");

实现驱动后:通过makefile来进行编译

-----------------------------------------


字符设备驱动模型:字符设备驱动怎么写
    驱动:
    对于一个驱动而言需要有对应的驱动功能

    作为字符设备驱动的要素:
    1、必须要有一个设备号,用于在内核中的众多设备驱动进行区分

    2、必须要有一个设备文件,用户必须知道设备驱动对应的设备节点(设备文件)

    3、驱动对设备的操作,与应用程序中的系统调用关联,其实就是文件操作
        应用空间调用open、read、write、ioctl函数,实际在驱动中调用对应的xxx_open、xxx_read等函数


    实现字符设备框架:
    
    a、作为一个字符设备驱动,必须有一个设备号----向内核申请设备号
    int register_chrdev(    unsigned int major,
            const char *name,
            const struct file_operations *fops)----注册设备号
    
    参数1:
        unsigned int major:主设备号,次设备号自动分配
        无符号整数
        
            设备号:32bit = 主设备号(12bit) + 次设备号(20bit)
            主设备号:表示同一类设备
            次设备号:表示同一类设备中的不同设备

    参数2:
        const char *name:描述一个设备驱动信息,自定义
        字符串首地址

    参数3:
        const struct file_operations *fops:文件操作对象,函数关联(使用结构体来存储驱动与应用程序的关联)
        结构体地址(指针)

    返回值:
        正确返回0,失败返回负数

    b、创建一个设备节点(设备文件),能够提供给应用操作设备驱动
        1、手动创建
        mknod    设备节点名    设备类型    主设备号    次设备号
        mknod    /dev/led2        c    250    0

        2、自动创建(通过udev/mdev机制)

        struct class * class_create(owner,name);
        参数1:
            owner:拥有者,一般THIS_MODULE
        参数2:
            name:字符串,描述信息
        返回值:
            struct class *
            信息结构体        

        //创建设备文件
        struct device *device_create(    struct class *class,
                    struct device *parent,
                    dev_t devt,
                    void *drvdata,
                    const char *fmt, ...)----创建设备节点
        参数1:
            struct class *class:class结构体,创建的设备文件的信息内容
            通过 class_create()函数创建
        参数2:
            struct device *parent:表示父类对象,一般直接写NULL
            结构体地址
        参数3:
            dev_t devt:设备号
            dev_t(unsigned long)

        参数4:
            void *drvdata:私有数据,一般填NULL
            
        参数5:
            const char *fmt, ...:设备文件名
            字符串首地址
            
        返回值:
            struct device *---------设备节点对象(设备文件描述)
            成功返回地址,失败返回NULL

    c、在驱动中实现文件io接口功能(与应用关联),应用程序调用文件io时,驱动程序也调用对应的文件io接口函数
    在结构体 struct file_operations 每一个成员变量都代表绑定一个系统调用(文件io)函数,只要对结构体中的成员赋值,就代表值绑定上一个文件io函数
    struct file_operations {
        struct module *owner;
        loff_t (*llseek) (struct file *, loff_t, int);
        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
        ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
        ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
        int (*iterate) (struct file *, struct dir_context *);
        unsigned int (*poll) (struct file *, struct poll_table_struct *);
        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);
        int (*flush) (struct file *, fl_owner_t id);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, loff_t, loff_t, int datasync);
        int (*aio_fsync) (struct kiocb *, int datasync);
        int (*fasync) (int, struct file *, int);
        int (*lock) (struct file *, int, struct file_lock *);
        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
        int (*check_flags)(int);
        int (*flock) (struct file *, int, struct file_lock *);
        ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
        ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
        int (*setlease)(struct file *, long, struct file_lock **);
        long (*fallocate)(struct file *file, int mode, loff_t offset,
              loff_t len);
        int (*show_fdinfo)(struct seq_file *m, struct file *f);
    };//函数指针的集合,
        每个函数指针赋值为函数地址,就代表当应用程序调用对应的文件io函数时,驱动就执行函数指针赋值的对应函数
    
    const struct file_operations fops = {
        .open = led_open,//当应用程序调用open时,驱动则执行结构体成员open对应的赋值函数
        .release = led_close,
    };

    
    d、应用程序操作驱动,应用程序调用文件io函数去控制驱动


    e、应用和驱动传递数据
        read、write
        copy_to_user
        copy_from_user

    f、驱动控制硬件,控制外设,其实就是控制地址,通过地址往寄存器写入、读出控制
        内核驱动是通过虚拟地址操作
        初始化硬件
        
        地址映射:
        void * ioremap(cookie,size);
        参数1:
            cookie:物理地址
        参数2:
            size:映射内容大小,字节

        返回值:
            返回映射成功后的虚拟内存地址
            操作虚拟内存地址中的内容就是操作对应的物理地址空间内容
        


        

1、编写字符设备驱动:能够控制硬件设备的驱动程序

    步骤:
    1、实现加载模块和卸载模块的函数实现
        module_init(led_init);
        module_exit(led_exit);

    2、在模块加载函数中:当内核加载驱动时,申请是一个字符设备驱动
        a、申请设备号,绑定文件io接口
            设备号:内核中区分和管理不同的字符设备
            register_chrdev(major,"led drv",&fops);
        b、创建设备文件
            //创建结构体
            struct class * cls = class_create(THIS_MODULE,"led class");
            //创建设备节点
            struct device * dev = device_create(cls,NULL,250<<20|0,NULL,"led");
    
        c、实现file_operations结构体,实现文件io接口----驱动程序能够和应用程序进行交互
            提供功能给应用程序使用(调用文件IO函数)
            const struct file_operations fops = {
                .release = led_close,
                .open = led_open,
                .read = led_read,
                .write = led_write,
            };

        d、硬件初始化-------驱动程序能够和硬件进行交互
            1、地址映射

            2、申请中断

            3、实现硬件的寄存器初始化

        e、实现硬件控制功能
            应用程序在调用文件IO函数时,会把参数传递给驱动对应的函数参数


    规范:
    1、驱动卸载rmmod清空申请

        在卸载入口中实现,清除
            与初始化逆序过程进行卸载
            //1、映射释放(中断释放)
            iounmap(映射的虚拟内存地址);----释放映射地址

            //2、释放设备文件
            void device_destroy(struct class * class,dev_t devt);
            //3、释放设备文件结构体
            void class_destroy(struct class * cls)

            /4、释放设备号
            void unregister_chrdev(unsigned int major,const char * name)

    2、面向对象的编程思想
        用一个结构体来表示一个对象(驱动对象)

        设计一个类型(结构体类型),描述设备驱动的信息
        
            //设计结构体类型来表示整个驱动对象(描述设备驱动信息)
            struct led_drv
            {
                unsigned int major;//主设备号
                dev_t devno;//设备号
                struct class * cls;//设备文件信息结构体
                struct device* dev;//设备文件信息
                unsigned int * gpx1con;//设备驱动映射的地址
                unsigned int * gpx1dat;
            };
            //定义全局设备对象(存储信息)
            struct led_drv  led;

    3、出错处理
        在执行驱动操作时,有可能会出现错误不正确情况,需要对错误问题进行处理
        ret = register_chrdev(led.major,"led drv",&fops);
        if(ret < 0)//出错处理
        {
            printk("register devno error\n");
            goto err_1;
            //return -1;
        }
            led.cls = class_create(THIS_MODULE,"led class");
        if(IS_ERR(led.cls))//判断出错
        {
            printk("class create error\n");
            goto err_2;
            //return -1;
        }
        ........
        err_5:
            iounmap(led.gpx1con);

        err_4:
            device_destroy(led.cls,led.devno);

        err_3:
            class_destroy(led.cls);

        err_2:
            unregister_chrdev(led.major,"led drv");

        err_1:    
            return -1;


------
实现蜂鸣器驱动

       

中断驱动---检测外部中断
    获取外设的数据内容,通过中断信号进行获取
    在驱动中设置外设为中断模式:当外设产生设定的特定信号(就是中断)
    在驱动中实现中断处理操作(函数)


1、中断号:就是一个号码,中断控制器管理所有中断的编号,外设连接的引脚就对应了引脚的中断控制器的中断号
    有硬件设备----设备的中断号
    驱动如何获取中断号:
    1、宏定义
        IRQ_EINT(中断号)
    2、设备树中描述,然后再驱动中获取
        arch/arm/boot/dts/exynos4412-fs4412.dts
        
        在设备树中添加硬件信息(包括中断)

        硬件:
        key3 ------GPX1_2--------EINT10

        在设备树中:arch/arm/boot/dts/exynos4x12-pinctrl.dtsi
        
        gpx1: gpx1 {
                                gpio-controller;
                                #gpio-cells = <2>;

                               interrupt-controller;
                                interrupt-parent = <&gic>;
                                interrupts = <0 24 0>, <0 25 0>, <0 26 0>, <0 27 0>,
                                                 <0 28 0>, <0 29 0>, <0 30 0>, <0 31 0>;
                                #interrupt-cells = <2>;
                        };

        在设备树中添加自己的硬件设备信息---添加key3节点-----描述当前设备的的信息内容(中断号)
        arch/arm/boot/dts/exynos4412-fs4412.dts:实现硬件描述(中断号)
        
        key3_node {
                                compatible = "key3";
                                interrupt-parent = <&gpx1>;
                               interrupts = <2 4>;//26
                };

        重新编译设备树
            make dtbs
            cp arch/arm/boot/dts/exynos4412-fs4412.dtb /tftpdir/

2、实现驱动工作----中断驱动应该做什么
    (外设产生中断,驱动要能检测到中断---申请中断(根据中断号))
    (处理外设产生的中断)
    在驱动中申请中断,实现中断处理

    a、获取到中断号
        获取设备树节点,返回值就是从设备树中找到的节点
        struct device_node *of_find_node_by_path(const char *path);

        从节点中获取到中断号,返回值就是中断号
        unsigned int irq_of_parse_and_map(struct device_node *dev,int index);

    b、申请中断
        typedef irqreturn_t (*irq_handler_t)(int, void *);----类型替换,irq_handler_t代表函数指针
        int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
        参数1:
            unsigned int irq:申请中断的中断号

        参数2:
            irqreturn_t (*)(int, void *)    ----   irq_handler_t
            irq_handler_t handler:函数指针,进行注册中断,当产生中断时调用对应的函数进行处理

        参数3:
            unsigned long flags:中断处理的触发方式
            
            #define IRQF_TRIGGER_NONE    0x00000000
            #define IRQF_TRIGGER_RISING    0x00000001
            #define IRQF_TRIGGER_FALLING    0x00000002
            #define IRQF_TRIGGER_HIGH    0x00000004
            #define IRQF_TRIGGER_LOW    0x00000008

        参数4:
            const char *name:字符串首地址,中断的描述信息
                /proc/inruppter
        参数5:
            void *dev:传递给参数2的函数进行自动调用的(作为参数2这个函数的参数)

        返回值:
            成功返回0,失败返回非0


        释放中断:
        void free_irq(unsigned int irq,void * dev_id)
        参数1:
            unsigned int irq
            中断号
        参数2:
            void * dev_id:与申请中断第五个参数保持一致

           

4、驱动中要实现把硬件产生的数据传递给用户(应用程序)

    a、驱动中获取到硬件的数据
        通过中断获取到数据(中断处理函数中)
        具体硬件对应着一个数据值
            key2------'a'
            key3------'q'

        从硬件的寄存器中获取数据值
            key3--------按下和抬起-------1/0
            读取key3的gpio寄存器---数据寄存器

    b、驱动传递给用户(应用)
        驱动在文件io接口实现中,完成传递给应用
        
        驱动在xxxx_read函数中将数据传递给用户
            ret = copy_to_user(buf,&data,1);


    c、用户如何拿到----编写应用程序
        

5、文件io模型
    阻塞io模型------休眠等待
    阻塞:当进程读取外部资源(数据),但是外部资源没有准备好,进程就进行休眠等待资源可用

    在应用中:
        read
        wirte
        accept
        scanf
        默认都是阻塞的

    驱动中实现阻塞:
        要创建等待队列头:
            wait_queue_head_t head;
            init_waitqueue_head(&head);
    
        1、在需要等待的位置(没有数据),就阻塞等待
            
            wait_event_interruptible(wq,condition)-----根据参数是否进行阻塞等待,完成阻塞等待

            参数1:
                wq:等待队列头,把当前进程加入到哪个等待队列中

            参数2:
                condition:是否执行阻塞等待的条件

                condition:真---不进行阻塞
                condition:假---进行阻塞

        2、合适位置进行阻塞唤醒
            wake_up_interruptible(&head);
   

文件io模型:
    1、阻塞:阻塞io,当有数据时读取数据,没有数据时阻塞等待


    2、非阻塞:在进行读写操作时,如果没有数据,就立即返回,如果有数据读取数据然后立即返回
        应用程序:设置为非阻塞打开
        int fd = open("/dev/key3",O_RDONLY | O_NONBLOCK);

        驱动添加:
        if((file->f_flags & O_NONBLOCK != 0) && (condition == 0))
            return -1;

    3、io多路复用
        
    
    4、异步通知(异步信号):当有数据的时候,驱动就会发送信号(SIGIO)给应用,应用就可以异步接收数据,而不用主动接收(可以去完成其他工作)

        a、应用程序----处理信号
            void catch_signal(int signo)
            {
                char num;
                if( read(fd,&num,1) < 0)
                {
                    printf("no data;");
                    return ;
                }

                printf("data is %c\n",num);
            }
            //1、设置信号处理方式
                signal(SIGIO,catch_signal);
            //2、设置当前进程为SIGIO信号的属主进程
                fcntl(fd,F_SETOWN,getpid());
        
            //3、将io模式设置为异步模式
                int flags = fcntl(fd,F_GETFL);
                flags |= FASYNC;//添加异步属性
                fcntl(fd,F_SETFL,flags);

        b、驱动程序---发送信号

            1、需要和应用进程进行关联-------信号要发送给谁
                实现fasync接口,在接口中进行关联
                int key_fasync (int fd , struct file * file, int no)
                {

                    //记录信号发送给哪个应用
                    return fasync_helper(fd,file,no,&(key.fasync));
                }
    
            2、在合适位置发送信号
                kill_fasync(&(key.fasync),SIGIO,POLLIN);

                
中断的下半部分:
    1、softirq:软中断,处理级别比较高,在内核机制中,需要修改内核源码功能
    2、tasklet:实际上就是内部调用了softirq
    3、workqueue:工作队列


    tasklet:
        a、初始化
            初始化任务队列
            void tasklet_init(struct tasklet_struct * t,void(* func)(unsigned long),unsigned long data)
            参数1:
                struct tasklet_struct * t :任务队列头节点
            参数2:
                void(* func)(unsigned long):下半部分的实现逻辑
            参数3:
                unsigned long data:参数2函数的参数
            
        b、在中断上半部分中,启动下半部分(放入内核线程中)
            tasklet_schedule(struct tasklet_struct * t)
            如:
            tasklet_schedule(&(key.t));

    workqueue:
        a、初始化
            初始化工作队列
            INIT_WORK(struct work_struct *work,void (*work_func_t)(struct work_struct *work));
            参数1:
                struct work_struct *work:初始化的工作队列对象(头节点)
            参数2:
                void (*work_func_t)(struct work_struct *work):工作队列下半部分实现的逻辑

        b、在中断上半部分中,启动下半部分(放入内核线程中)--启动
            schedule_work(struct work_struct *work);
            如:
            schedule_work(&(key.mywork));

总线模型:
    驱动框架:
    0、声明实现入口函数(module_init、module_exit)
    1、申请设备号(register_chrdev)
    2、创建设备节点(class_create、device_create)
    3、硬件初始化
        ioremap地址映射
        中断申请
    4、实现文件IO接口

    总线模型:
        总线bus
        驱动driver
        设备device

    总线bus:
        struct bus_type:总线对象,描述一条总线,管理device、driver,进行匹配
        
        struct bus_type
        {
            const char        *name;:总线名字
            int (*match)(struct device *dev, struct device_driver *drv);总线调用匹配设备和驱动,返回值就表示匹配成功与否
        };

        注册总线:
            int bus_register(struct bus_type * bus);
            参数:
                struct bus_type * bus:总线对象
        注销总线:
            void bus_unregister(struct bus_type * bus);

    驱动driver:
        struct device_driver :驱动对象,描述一个驱动,对驱动进行说明
    
        struct device_driver {
            const char        *name;:驱动的名字
            struct bus_type        *bus;总线对象,表示要把驱动注册到哪条总线
            int (*probe) (struct device *dev);如果匹配成功,则调用该驱动的probe函数,创建驱动(申请设备号。。。)
            int (*remove) (struct device *dev);当设备对象和驱动对象移除总线时会调用
        }
        注册驱动到总线:
            int driver_register(struct device_driver * drv);
        从总线上注销:
            void driver_unregister(struct device_driver * drv);

    设备device:
        struct device {
            struct kobject kobj;//所有对象的父类
            const char        *init_name;设备名
            struct bus_type    *bus;总线对象,表示要把设备注册到哪条总线
            void        *platform_data;自定义数据,指向任意类型,可以存储设备信息
            void    (*release)(struct device *dev);设备对象从总线移除时会调用
        };
        注册设备到总线:
            int device_register(struct device * dev);
        从总线上注销:
            void device_unregister(struct device * dev);
   

文件io模型:
    1、阻塞:阻塞io,当有数据时读取数据,没有数据时阻塞等待


    2、非阻塞:在进行读写操作时,如果没有数据,就立即返回,如果有数据读取数据然后立即返回
        应用程序:设置为非阻塞打开
        int fd = open("/dev/key3",O_RDONLY | O_NONBLOCK);

        驱动添加:
        if((file->f_flags & O_NONBLOCK != 0) && (condition == 0))
            return -1;

    3、io多路复用
        
    
    4、异步通知(异步信号):当有数据的时候,驱动就会发送信号(SIGIO)给应用,应用就可以异步接收数据,而不用主动接收(可以去完成其他工作)

        a、应用程序----处理信号
            void catch_signal(int signo)
            {
                char num;
                if( read(fd,&num,1) < 0)
                {
                    printf("no data;");
                    return ;
                }

                printf("data is %c\n",num);
            }
            //1、设置信号处理方式
                signal(SIGIO,catch_signal);
            //2、设置当前进程为SIGIO信号的属主进程
                fcntl(fd,F_SETOWN,getpid());
        
            //3、将io模式设置为异步模式
                int flags = fcntl(fd,F_GETFL);
                flags |= FASYNC;//添加异步属性
                fcntl(fd,F_SETFL,flags);

        b、驱动程序---发送信号

            1、需要和应用进程进行关联-------信号要发送给谁
                实现fasync接口,在接口中进行关联
                int key_fasync (int fd , struct file * file, int no)
                {

                    //记录信号发送给哪个应用
                    return fasync_helper(fd,file,no,&(key.fasync));
                }
    
            2、在合适位置发送信号
                kill_fasync(&(key.fasync),SIGIO,POLLIN);

                
中断的下半部分:
    1、softirq:软中断,处理级别比较高,在内核机制中,需要修改内核源码功能
    2、tasklet:实际上就是内部调用了softirq
    3、workqueue:工作队列


    tasklet:
        a、初始化
            初始化任务队列
            void tasklet_init(struct tasklet_struct * t,void(* func)(unsigned long),unsigned long data)
            参数1:
                struct tasklet_struct * t :任务队列头节点
            参数2:
                void(* func)(unsigned long):下半部分的实现逻辑
            参数3:
                unsigned long data:参数2函数的参数
            
        b、在中断上半部分中,启动下半部分(放入内核线程中)
            tasklet_schedule(struct tasklet_struct * t)
            如:
            tasklet_schedule(&(key.t));

    workqueue:
        a、初始化
            初始化工作队列
            INIT_WORK(struct work_struct *work,void (*work_func_t)(struct work_struct *work));
            参数1:
                struct work_struct *work:初始化的工作队列对象(头节点)
            参数2:
                void (*work_func_t)(struct work_struct *work):工作队列下半部分实现的逻辑

        b、在中断上半部分中,启动下半部分(放入内核线程中)--启动
            schedule_work(struct work_struct *work);
            如:
            schedule_work(&(key.mywork));

总线模型:
    驱动框架:
    0、声明实现入口函数(module_init、module_exit)
    1、申请设备号(register_chrdev)
    2、创建设备节点(class_create、device_create)
    3、硬件初始化
        ioremap地址映射
        中断申请
    4、实现文件IO接口

    总线模型:
        总线bus
        驱动driver
        设备device

    总线bus:
        struct bus_type:总线对象,描述一条总线,管理device、driver,进行匹配
        
        struct bus_type
        {
            const char        *name;:总线名字
            int (*match)(struct device *dev, struct device_driver *drv);总线调用匹配设备和驱动,返回值就表示匹配成功与否
        };

        注册总线:
            int bus_register(struct bus_type * bus);
            参数:
                struct bus_type * bus:总线对象
        注销总线:
            void bus_unregister(struct bus_type * bus);

    驱动driver:
        struct device_driver :驱动对象,描述一个驱动,对驱动进行说明
    
        struct device_driver {
            const char        *name;:驱动的名字
            struct bus_type        *bus;总线对象,表示要把驱动注册到哪条总线
            int (*probe) (struct device *dev);如果匹配成功,则调用该驱动的probe函数,创建驱动(申请设备号。。。)
            int (*remove) (struct device *dev);当设备对象和驱动对象移除总线时会调用
        }
        注册驱动到总线:
            int driver_register(struct device_driver * drv);
        从总线上注销:
            void driver_unregister(struct device_driver * drv);

    设备device:
        struct device {
            struct kobject kobj;//所有对象的父类
            const char        *init_name;设备名
            struct bus_type    *bus;总线对象,表示要把设备注册到哪条总线
            void        *platform_data;自定义数据,指向任意类型,可以存储设备信息
            void    (*release)(struct device *dev);设备对象从总线移除时会调用
        };
        注册设备到总线:
            int device_register(struct device * dev);
        从总线上注销:
            void device_unregister(struct device * dev);
   


总线模型:
    led驱动

    led驱动代码:
        只有一份

    led设备信息:
        每个led都有一个设备信息

    总线:
        一条总线,挂载多个驱动和设备

    
    编写针对多个led的驱动程序

--------
平台总线

    用于升级
    三星:2410、2440、6410、s5pc100、s5pv210、exynos4412

    在平台进行升级时(硬件),部分的模块的控制方式,基本上是类似的,但是模块的地址有区别

        gpio逻辑控制
            1、配置gpio输入输出功能 gpxxcon
            2、给gpio数据寄存器设置高低电平 gpxxdat

        uart逻辑控制
            1、设置功能:115200、8n1


        基本操作都是类似
        但是地址不同
    
    问题:
        当升级平台时,对于相似的设备驱动,需要编写多次驱动代码(没有总线),会造成大量重复的代码


    解决:
        引入平台总线

        device(设备:设备信息--地址、中断号)和driver(驱动:操作逻辑----驱动功能)进行分离
        在升级的时候只用修改device中的设备信息即可

        实现一个driver代码就能够驱动多个平台相似的模块,修改很少的内容


    总线bus:
        不需要自己创建,在内核中已经使用
        //平台总线对象
        struct bus_type platform_bus_type = {
            .name        = "platform",
            .dev_groups    = platform_dev_groups,
            .match        = platform_match,
            .uevent        = platform_uevent,
            .pm        = &platform_dev_pm_ops,
        };

        平台总线的匹配方法:platform_match
        1、进行匹配platformdriver中的id_table,在驱动对象中的id_table就是列出支持哪些平台与平台对象名进行匹配
        2、匹配驱动对象的名字和设备对象的名字
            struct platform_device *pdev = to_platform_device(dev);
            struct platform_driver *pdrv = to_platform_driver(drv);
            if (pdrv->id_table)
                return platform_match_id(pdrv->id_table, pdev) != NULL;

            /* fall-back to driver name match */
            return (strcmp(pdev->name, drv->name) == 0);

    设备device:
        平台对象:
        struct platform_device {
            const char    *name;设备对象名,用于做匹配
            int        id;//一般都是直接给-1
            struct device    dev;//父类对象device
            u32        num_resources;//资源个数
            struct resource    *resource;//资源(地址、中断):设备信息
        }

        /资源/
        struct resource {
            resource_size_t start;
            resource_size_t end;
            const char *name;
            unsigned long flags;
            struct resource *parent, *sibling, *child;
        };

        注册设备对象到平台总线:
        int platform_device_register(struct platform_device * pdev);
        //注销平台设备
        void platform_device_unregister(struct platform_device * pdev);


    驱动driver:
        平台驱动对象:
        struct platform_driver {
            int (*probe)(struct platform_device *);匹配成功后会调用
            int (*remove)(struct platform_device *);//在匹配成功后,移除device设备,会调用
            struct device_driver driver;
                | const char        *name;//驱动对象名字,可以用于匹配
            const struct platform_device_id *id_table;//如果driver支持多个平台,在id_table中写出支持哪些平台,平台总线会用这个对象值用于与设备对象名字进行匹配
        }
        
        driver-----平台信息的描述(支持什么平台)
        struct platform_device_id {
            char name[PLATFORM_NAME_SIZE];//平台设备名,用于进行匹配
            kernel_ulong_t driver_data;
        };
        
        注册驱动对象到平台总线:
        int platform_driver_register(struct platform_driver * drv);
        注销:
        void platform_driver_unregister(struct platform_driver * drv)

iic总线


mpu6050加速度陀螺仪

mpu6050-----I2C_SDA5和I2C_SCL5与芯片连接

通过芯片控制对应的i2c就可以与mpu6050传输数据
芯片i2c:
    引脚:
        GPB_2
        GPB_3

    i2c控制器:
        i2c5


    I2C5---------0x138B_0000
        I2CCON------0x0000
        I2CCON控制寄存器用于控制是否发出ACK信号、设置发送器的时钟、iic中断

        I2CSTAT------0x0004
        I2CSTAT状态寄存器用于选择iic的工作模式,发出S信号,发送P信号,使能发送/接收功能,并标识各种状态

        I2CADD------0x0008
        
        I2CDS--------0x000c
        发送/接收移位寄存器


#define SMPLRT_DIV  0x19 //陀螺仪采样率,典型值:0x07(125Hz)
#define CONFIG   0x1A //低通滤波频率,典型值:0x06(5Hz)
#define GYRO_CONFIG  0x1B //陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s)
#define ACCEL_CONFIG 0x1C //加速计自检、测量范围及高通滤波频率,典型值:0x01(不自检,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
#define GYRO_ZOUT_L  0x48
#define PWR_MGMT_1  0x6B //电源管理,典型值:0x00(正常启用)
#define WHO_AM_I  0x75 //IIC地址寄存器(默认数值0x68,只读)

iic子系统框架

应用
----------------------------------------------------------
i2c driver:从设备驱动层
    需要和应用进行交互
    封装数据,不知道如何发送给硬件
----------------------------------------------------------
i2c core:i2c核心层 维护i2c bus 包括 i2c driver 、 i2c device链表

----------------------------------------------------------
i2c adapter:i2c 控制层,初始化i2c控制器

    完成将数据写入或读出----从设备
    不知道数据是什么,但是直到如何操作硬件

硬件信息-----device设备----添加到设备树

模板:
    在内核 arch/arm/boot/dts/exynos4.dtsi
            i2c_5: i2c@138B0000 {
                        #address-cells = <1>;
                        #size-cells = <0>;
                       compatible = "samsung,s3c2440-i2c";
                        reg = <0x138B0000 0x100>;
                        interrupts = <0 63 0>;
                        clocks = <&clock 322>;
                        clock-names = "i2c";
                        status = "disabled";
                };


        i2c@13860000 {
                            #address-cells = <1>;
                            #size-cells = <0>;
                            samsung,i2c-sda-delay = <100>;
                            samsung,i2c-max-bus-freq = <20000>;
                            pinctrl-0 = <&i2c0_bus>;
                            pinctrl-names = "default";
                            status = "okay";

                            s5m8767_pmic@66 {
                                    compatible = "samsung,s5m8767-pmic";
                                    reg = <0x66>;
            }
        }


    根据参考,添加新的i2c从设备:添加i2c5控制器和它包含的从设备
        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";
                            status = "okay";

                            mpu6050@68 {
                                    compatible = "invensense,mpu6050";
                                    reg = <0x68>;
            };
        };


实现 i2c driver 驱动 编写:

    i2c驱动对象:
    struct i2c_driver
    {
        int (*probe)(struct i2c_client *, const struct i2c_device_id *);
        int (*remove)(struct i2c_client *);
        struct device_driver driver;//父类
            |
            const char        *name;//驱动名字
            const struct of_device_id    *of_match_table;//与设备树设备进行匹配
                /*struct of_device_id
                {
                    char    name[32];
                    char    type[32];
                    char    compatible[128];//设备树描述
                    const void *data;
                }*/

    }

    注册驱动对象到i2c总线:
    int i2c_add_driver(struct i2c_driver *driver);
    注销驱动对象:
    void i2c_del_driver(struct i2c_driver *driver);

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

驱动移植学习心得 的相关文章

随机推荐

  • Spring Boot 实现定时任务动态管理,太爽了!

    一 功能说明 SpringBoot的定时任务的加强工具 实现对SpringBoot原生的定时任务进行动态管理 完全兼容原生 Scheduled注解 无需对原本的定时任务进行修改 二 快速使用 具体的功能已经封装成SpringBoot sta
  • yolov5 烟雾和火焰检测

    视频实时多人姿态估计 cpu fps33 实时视频动作检测 action detection 基于人体姿态的跌倒检测 yolov5 烟雾和火焰检测 文章用到的云gpu详细使用说明 随着社会经济的高速发展 工业 企业园区 住宅日益增多 存在一
  • 交叉编译eigen3.2.10至ARM架构

    交叉编译eigen3 2 10至ARM架构 1 下载交叉编译链 PC机为x86架构 目标平台为ARM架构 首先需要安装x86至ARM平台的交叉编译链 需要注意的是 编译链上C库的版本需要和目标平台上的C库版本兼容 我起初参考其他博客直接ap
  • 运放分析--虚短与虚断

    虚短与虚断 1 虚短 如图1所示 虚短是指运放的输入端V 和V 可视为电压差很小 即近似相等 V V 由于并没有实际的物理连接 故我们称其为虚短 以区别物理连接的短路 若其中一端接地 则另一端在必要时 可认为虚地 2 虚断 由于运放是高阻抗
  • ScheduledThreadPoolExecutor 线程池例子

    ScheduledThreadPoolExecutor 线程池例子 一 ScheduledThreadPoolExecutor 使用 1 使用示例 提交任务 简单例子 二 ScheduledThreadPoolExecutor 原理 1 D
  • android状态栏一体化(沉浸式状态栏)

    Android 沉浸式状态栏 状态栏一体化 透明状态栏 仿ios透明状态栏 http blog csdn net jdsjlzx article details 50437779 注 状态栏的字体颜色位白色 如果状态栏背景为白色 上面的博客
  • Easyui入门(二)

    Easyui入门之Tree后台实现 tree的组件简介 案例1 运行结果 2 tree组件工具类的实现思路 预热 方案 代码 链接 代码2 正式从数据库拿数据写 代码 代码2 总结 tree的组件简介 静态的html方式 缺点 如果树形结构
  • C++中类的静态成员变量

    在C语言中 我们知道有static静态变量 生命周期与作用域都跟普通变量有所不同 而在C 的类中 也有静态成员变量同时还有静态成员函数 先来看看C 中静态成员变量与静态成员函数的语法 include
  • 润和软件推出HarmonyOS物联网系列模组Neptune,助力Harmony生态

    在2020 第十七届 中国物联网产业大会上 HarmonyOS首批官方合作伙伴润和软件宣布推出HarmonyOS智能硬件新品 支持HarmonyOS的物联网系列模组Neptune HH SLNPT10x 该系列模组使用的芯片由润和软件HiH
  • C语言,打印杨辉三角

    include
  • 【编译原理】三地址码

    三地址码 编译器构造 编译器的结构 中间语言 中间语言表达式 逆波兰 RPN 形式 图形 语义树 三地址码表达形式 四地址码表达形式 三地址码 三地址码 TAC 指令 三地址码的使用和特点 文字表 优化阶段 编译器构造 编译器的结构 语义检
  • powershell get-date计算指定日期及格式化

    get date format yyyyMMdd 获取当天日期并格式化为20200107的格式 get date UFormat V 获取当天是本年度的第几周 这里有一个bug 就是每周一获取到的还是上周 get date adddays
  • Ant-Design-Pro小试:react开发步骤(mock数据)

    1 router config js path train name train icon profile routes profile path train list name list component Train List 2 me
  • object-c万能解决bug思路

    有关运算符重载 C 支持运算符重载 但 Objective C 中不支持 然而 Objc 中可以看到下面的用法 id obj dict keyStr 它和 id obj dict objectForKey keyStr 等价 这里的 的用法
  • java综合技术分享

    1 心跳机制 1 1心跳包机制 跳包之所以叫心跳包是因为 它像心跳一样每隔固定时间发一次 以此来告诉服务器 这个客户端还活着 事实上这是为了保持长连接 至于这个包的内容 是没有什么特别规定的 不过一般都是很小的包 或者只包含包头的一个空包
  • stack queue free-lock implate

    https github com kayaklee libhalog blob master test clib hv sample lifo cpp https github com kayaklee libhalog blob mast
  • thrift源码解析之server

    文章目录 前言 概述 TSimpleServer serve 1 listen 2 accept 3 newlyConnectedClient TNonblockingServer serve 1 registerEvents 1 赋值us
  • Java中Thread类的基本用法

    目录 一 创建线程的方式 1 继承Thread类 2 实现Runnable接口 3 匿名内部类中创建Thread子类对象 4 匿名内部类中创建Runnable子类对象 5 lambda表达式创建Runnabl子类对象 二 Thread的常见
  • netty 系列之:java 中的 base64 编码器

    简介 什么是 Base64 编码呢 在回答这个问题之前 我们需要了解一下计算机中文件的分类 对于计算机来说文件可以分为两类 一类是文本文件 一类是二进制文件 对于二进制文件来说 其内容是用二进制来表示的 对于人类是不可立马理解的 如果你尝试
  • 驱动移植学习心得

    系统移植 把操作系统 Linux 能够在芯片 板子 上运行 目标 在开发板上运行操作系统 嵌入式系统 linux 以应用为中心 把软硬件进行裁剪 适用于应用的专用计算机系统 1 交叉编译环境搭建 开发主机 编译工具 针对开发板的编译工具 a