zoukankan      html  css  js  c++  java
  • 笔记

    day_1

    主题: 1。嵌入式概况
    2。linux内核概况
    3。linux内核的编译
    4。linux下的模块

    1。2

    3。内核的编译
    (1)配置
    $>make menuconfig
    通过图形化的配置界面,决定如何处理内核的各个功能部分。配置完成后,所有的配置选项包存在.config文件中。

    (2)编译内核和模块
    $>make
    根据.config的内容,决定应该编译哪些asm/c文件。
    通过不同的配置选项,可以决定代码是不编译,还是编译到zImage中,还是编译为.ko

    (3)安装模块
    $>make modules_install
    将编译出来的.ko安装到/lib/modules/目录中

    (4)安装内核
    $>make install
    将zImage安装到/boot目录下,并且修改/boot/grub/grub.conf文件

    安装成功后,可以重新reset机器,在引导内核时,选择新安装的内核。如果有问题,可以重新make menuconfig。


    4。模块
    模块实际上是一个容器的概念,一个模块中可以包含驱动,也可以包含新的文件系统,协议栈等。

    如果模块对应的c代码在内核源代码树,那么生成的模块称为内部模块;
    如果模块对应的c代码在其他位置,那么模块称为外部模块。

    今天先讲外部模块。

    参考x86-drv/01mod/mod_test01.c和Makefile

    day_2

    主题: 1。模块的其他内容说明
    2。模块的符号导出
    3。模块的参数
    4。从内核中导出信息:printk
    5。用户态了解内核信息:/proc下的文件
    6。list_head的使用

    作业:
    1。掌握module_param_array宏的使用,自己试着写个例子


    1.模块的其他内容
    (1)模块相关工具
    $>insmod xxx.ko
    手工加载一个模块

    $>rmmod xxx
    手工卸载一个模块

    $>lsmod
    查看内核已经加载的模块

    $>modinfo
    参看某个模块的信息

    $>modprobe xxx
    自动加载模块。只能加载已经安装到/lib/modules下的模块。
    还可以用modprobe卸载模块:
    $>modprobe -r xxx
    如果xxx.ko依赖的模块计数为0,则一起卸载。


    (2)查看printk的信息
    $>dmesg
    $>dmesg -c /* 查看信息后,将信息清空 */


    2. 模块的符号导出
    为了避免命名空间的污染,内核规定ko模块中的符号,无论加不加static,都是局部的。
    如果要将符号明确导出为全局的,则需要用宏:
    EXPORT_SYMBOL(xxx);
    例如:
    int abc(...)
    {
    ...
    }
    EXPORT_SYMBOL(abc);

    注意!如果c文件不是编译为模块,而是编译到zImage中,那么符号的权限仍然遵循普通c的要求,加上static的为局部,不加为全局。

    还有一个类似的宏:
    EXPORT_SYMBOL_GPL(xxx);
    利用该宏导出的符号,只能被具有GPL许可证的模块使用。


    3. 模块的参数
    参考01mod/mod_test03.c


    4. printk
    printk在内核的任意地方都可以使用,将字符串添加优先级后,写入内核的一个缓冲区(该缓冲区称为log
    buffer,默认128KB)

    可以用dmesg观察;或者回到纯文本控制台,可以自动显示级别较高的printk信息


    5. proc文件

    day_3

    主题: 1。完成list_head的例子
    2。VFS的核心结构体
    3。char/block/网络设备的特征
    4。char驱动的设计
    5。基于内存缓冲区的char驱动例子

    作业:完成多缓冲区的char设备驱动

    1.参考02proc/proc_test03.c


    2. VFS的核心结构体
    定义在<linux/fs.h>

    (1)super_block
    结构体的名字来源于ext2文件系统。VFS用super_block结构体来记录已挂载磁盘的分区信息。
    也就是说,当用户态mount一个磁盘分区时,VFS会在内核分配并初始化一个super_block结构体,结构体中记录分区的信息。super_block将一直存在到用户执行umount

    如果用户态mount的是procfs等非磁盘文件系统,则VFS也会分配一个super_block,但其中的分区信息由文件系统负责填充(一般没意义)

    所以,super_block是和用户态的mount动作一一对应。


    2.inode
    inode是VFS创建的结构体,用于记录文件信息,比如文件的大小,属主,atime/ctime/mtime。

    当打开一个文件时,VFS会创建inode,inode的成员用从磁盘上读取的文件信息来赋值。

    所以,inode应该和实际打开的文件对应。如果有两个人同时打开一个文件,那么VFS只会维护一个inode。

    inode中有两个和char设备文件相关的成员:
    i_rdev: 记录设备的设备号
    i_cdev: 指向一个cdev结构体,该结构体记录char设备的信息


    3. file
    和用户态的open操作一一对应。file中记录文件打开已经使用过程中的一些标志信息。
    如果对同一文件open多次,那么VFS会创建多个file。


    4. file_operations
    VFS要求char驱动实现若干设备的访问函数,然后将这些函数的指针集中到file_operations中。

    通常char驱动应该实现的操作有:
    open
    release
    read
    write
    unlocked_ioctl
    (课堂讲)

    可能需要实现的操作有:
    lseek
    poll
    mmap
    fasync
    (参考LDD3书)


    3. 三种设备类型的特征


    4. char驱动的设计
    (1)了解硬件
    可能一个驱动会支持多个类似的硬件

    (2)设计和硬件对应的私有结构体
    私有结构体完全由驱动人员自行设计,原则是将使用设备时涉及的各类信息都集中到私有结构体中。比如设备号,设备寄存器的地址,中断号,锁,等待队列,计数等。

    内核不会规定私有结构体如何设计。如果一个驱动要支持多个设备,那么一般要求定义私有结构体。

    (3)初始化私有结构体以及硬件

    (4)为设备分配设备号
    原则是设备号必须唯一。如果一个驱动支持多个设备,那么应该为这些设备采用相同的主设备号,次设备号任意。

    (5)准备file_operations
    根据设备需求,提供xxx_open, xxx_release等操作函数

    (6)将设备号和file_operations集中到cdev结构体中,注册到VFS

    (7)如果用户态要访问硬件,还有通过mknod在/dev/下创建设备节点


    5. 基于缓冲区的char驱动例子
    参考03char/char_test01.c

    day_4

    主题: 1。完成支持多设备的char驱动
    2。构建在开发板上运行的linux内核
    3。测试开发板上的内核和文件系统是否可运行
    4。将写好的驱动放置到内核代码树中,编译进zImage运行

    作业:1. 使用MEM_RESIZE命令
    2. 仔细看看make menuconfig的各个选项,尤其是和6410相关的。另外,再看看内核中6410实际的驱动文件都是哪些。
    (不一定今天完成,但最好抽时间做一下)


    1. 多设备驱动
    参考03char/char_test03.c


    2.开发板上内核编译
    (1)使用默认的开发板配置文件
    $>cd /home/zhang/kernel/linux-2.6.28_smdk6410
    $>cp smdk6410_config .config
    注意,各类型开发板默认配置文件的路径一般应该在arch/arm/configs/目录下。从其中挑一个和当前开发板最接近的就可以。

    (2)利用menuconfig对选项进行调整
    $>make menuconfig
    必须确保选项和开发板的情况一致。
    修改部分:
    a.取消模块的支持,这样,所有代码全部编译进zImage
    b.进入Device Drivers-->Graphics support-->support framebuffer。。。进去将LCD屏从800*480修改为480*272

    (3)编译
    $>make
    make之前,先修改Makefile第194行,将CROSS_COMPILE修改为:
    CROSS_COMPILE := arm-linux-


    3. 测试


    4。将自己的驱动编译到zimage中
    (1)创建目录.../drivers/arm-drv/
    $>mkdir drivers/arm-drv/
    以后课程中自己写的在开发板上运行的驱动,统一放在该目录下

    (2)将自己写的缓冲区的驱动拷贝到arm-drv/目录下
    $>cp .../03char/char_test0*.c .../drivers/arm-drv/

    (3)在arm-drv/中创建Kconfig
    参考drivers/arm-drv/Kconfig

    (4)修改上层Kconfig
    修改arch/arm/Kconfig,在1224行加入:
    source "drivers/arm-drv/Kconfig"

    (5)利用图形化的配置界面选择将新的驱动编译进内核
    $>make menuconfig
    选择完后,在.config文件中应该见到新的CONFIG_UP6410_DRV等选项。

    (6)在arm-drv/中创建Makefile
    参考arm-drv/Makefile

    (7)修改上层的Makefile
    修改drivers/Makefile,在第75行加入:
    obj-$(CONFIG_UP6410_DRV) += arm-drv/

    (8)重新make内核
    $>make
    生成的zImage在arch/arm/boot目录下


    4. 新内核的测试
    (1)将zImage拷贝到tftp共享目录
    $>cp arch/arm/boot/zImage /tftpboot

    (2)准备好文件系统,用于NFS登录
    $>tar xzvf /home/zhang/rootfs/nfsboot.tar.gz -C /
    然后修改/etc/exports,增加/nfsboot为NFS共享目录

    (3)启动开发板
    $>minicom -s

    $>tftp 50008000 zImage
    $>bootm
    $>setenv bootargs noinitrd root=/dev/nfs nfsroot=192.168.1.254:/nfsboot/qt_root ip=192.168.1.20 init=/linuxrc console=ttySAC0,115200 mem=128M

    如果登录成功,可以运行一下/usr/local/multimedia_test应用程序
    还可以验证char_test01.c/char_test02.c是否可运行
    自己mknod..还可以查看/proc/mem_test

    day_5

    主题: 1。驱动如何访问寄存器以及6410的物理地址空间
    2。linux下如何访问有物理地址的寄存器
    3。为led灯设计char驱动

    1。如何访问寄存器

    2。物理寄存器的访问
    (1)将手册中找到的物理地址转换为虚拟地址
    例子:
    #include <linux/ioport.h>
    #define PHY_BASE 0x7E005000 /* RTC */
    #define PHY_SIZE 0x7E005090

    static void __iomem *vir_base;

    /* 将物理地址映射到虚拟地址
    * 如果返回NULL,则映射失败 */
    vir_base = ioremap(PHY_BASE, /* 物理基地址 */
    PHY_SIZE); /* 物理地址范围 */
    if (!vir_base)
    return -EIO;


    (2)寄存器的访问
    寄存器的访问应该使用虚拟基地址+偏移的模式

    例子:
    #include <linux/io.h>

    8位寄存器
    char value;
    value = readb(vir_base + offset);
    writeb(value, (vir_base+offset));

    16位寄存器
    short value;
    value = readw(vir_base+offset);
    writew(value, (vir_base+offset));

    32位寄存器
    int value;
    value = readl(vir_base+offset);
    writel(value, (vir_base+offset));

    64位寄存器
    long long value;
    value = readq(vir_base+offset);
    writeq(value, (vir_base+offset));


    (3)释放虚拟地址的映射
    如果驱动退出,则必须将映射到的虚拟地址释放
    例子:
    #include <linux/ioport.h>
    iounmap(vir_base);

    3。char驱动
    要求:
    1。开发板上有4个LED灯,完成标准的char驱动,在用户态创建

    #include <linux/xxx.h>
    #include <asm/xxx.h>


    对于6410开发板来说,arch/arm下的重要目录是:
    1. arch/arm/mach-s3c6410/
    其中的mach-smdk6410.c为开发板的初始化文件

    2. arch/arm/plat-s3c/
    三星各类型处理器公用的一些代码

    3. arch/arm/plat-s3c64xx/
    三星64系列处理器得到公用代码

    根据目录的分布,三星为中断设置中断号所准备的头文件,应该在plat-s3c64xx目录中。具体为:
    plat-s3c64xx/include/plat/irqs.h

    6410的中断号的分布:
    0~15: 保留给老式的ISA设备
    16~31: 分配给UART0到UART3
    32~95: 为VIC支持的每个中断源各分配一个中断号
    96~100: 为5个定时器准备的中断号。对于6410非常重要的是timer4对应的100号中断
    101~128: 外部中断组0的中断号(group0中共28个中断)
    比如开发板上的按键以及DM9000连接的中断都是属于group0的
    129~227: 外部中断组1到9的中断号(共99个)

    NR_IRQS: 228

     day_6

    主题: 1。讲评LED的例子
    2。中断处理架构
    3。中断处理函数
    4。内核中的时间
    5。基于开发板的按键中断控制LED的例子

    1.led的例子
    参考arm-drv/led_test02.c


    2. 中断架构
    核心结构体:
    (1)irq_desc
    定义在<linux/irq.h>
    和中断号一一对应。记录中断的状态,以及对应的中断处理函数。
    内核在开机时负责分配一个irq_desc结构体的数组,数组共有NR_IRQS个成员。

    (2)irqaction
    定义在<linux/interrupt.h>
    是中断处理函数的封装结构体。每个irqaction对应一个中断处理函数。
    在中断处理函数的注册函数中自动分配。
    由于中断支持共享,所以一个中断号可能有多个处理函数。
    hander: 中断处理函数
    flags: 中断的标志


    (3)irq_chip
    定义在<linux/irq.h>
    irq_chip中包含enable/disable/ack/mask等函数,用于为给定的中断号提供关闭,掩码等服务。对于6410来说,这些服务需要访问VIC或GPIO中对应的寄存器。

    理论上来说,一个irq_chip就可以为所有中断号提供服务。但为了加快中断的处理,通常,会为一组服务方式类似的中断号提供对应的irq_chip

    三星在移植6410时,构建的irq_chip以及对应的函数在arch/arm/plat-s3c64xx/目录,如irq.c和irq-eint.c


    3。中断处理函数
    类型在<linux/interrupt.h>中定义:
    typedef irqreturn_t (*irq_handler_t)(int, void *);
    中断处理函数的返回值只有IRQ_NONE和IRQ_HANDLED两个。如果处理了中断则返回IRQ_HANDLED,否则返回IRQ_NONE.

    中断处理函数调用时遵循原则:
    a. 可嵌套不可重入
    b. 不可睡眠(不能调用任何可能引起睡眠的函数)
    kmalloc(size, GFP_KERNEL);


    (1)中断的注册
    例子:
    #include <linux/interrupt.h>

    /* 中断处理函数 */
    irqreturn_t my_handler(int irq, void *dev_id)
    {
    ...
    }

    /* 中断的注册 */
    int ret;
    unsigned long flags = IRQF_SHARED |
    IRQF_SAMPLE_RANDOM |
    IRQF_TRIGGER_FALLING;

    ret = request_irq(irq, /* 中断号 */
    my_handler, /* 中断处理函数 */
    flags, /* 中断的标志 */
    "abc", /* 中断处理函数的名字,出现在/proc/interrupts文件中 */
    dev_id); /* 非0的参数,会传递给中断处理函数,一般应该把驱动的私有结构体传进来 */

    (2)中断的注销
    #include <linux/interrupt.h>
    free_irq(irq, dev_id);


    4. 按键的例子
    自己回去写。。。


    5. 时间
    tick:每个定时器中断间的间隔
    HZ:每秒钟硬件定时器中断的发生次数
    1 tick = 1/HZ秒
    内核很多定时和延迟的操作,都是基于tick的

    linux内核的运行队列

    day_7

    主题: 1。讲评按键中断的例子
    2。linux时间的其他内容
    3。内核的延迟
    4。内核中的定时

    作业:
    1。看wait_event宏的实现
    2. 利用timer_list实现按键驱动例子的去抖


    1. 按键中断例子
    参考drivers/arm-drv/key_test02.c

    2. 时间的其他内容
    tick/HZ
    内核有一个全局变量jiffies,记录开机以后经过的tick的数量。
    用jiffies/HZ,可以得到开机后到现在经过的秒数。这个时间称为相对时间。

    jiffies的类型为unsigned long.如果HZ为1000,那么肯能49。7天会溢出一次。
    为了避免这一情况,内核还提供了64位的jiffies:
    jiffies_64

    绝对时间:从1970年1月1日0时0分0秒到现在经过的时间。
    记录绝对时间要使用timeval或timespec结构体
    内核获得绝对时间的方式:
    例子:
    #include <linux/time.h>
    struct timeval tval;
    struct timespec tspec;

    /* 从内核获取绝对时间 */
    do_gettimeofday(&tval);
    getnstimeofday(&tspec);

    参考x86-drv/04time/time_test.c


    3. 内核的延迟
    延迟的原因是当前程序由于某些条件不满足,无法继续执行。此时需要延迟。

    (1)等待给定时间的延迟
    如果进程的条件只要等待足够的时间就可以满足,那么这种延迟可以称为等待给定时间的延迟。

    a.短延迟
    例子:
    #include <linux/delay.h>
    ndelay(xxx); /* xxx为延迟的ns数 */
    udelay(xxx);
    mdelay(xxx);
    基于us或ms的延迟,常常用于连续操作寄存器间的间隔。上述几个延迟函数,都是基于忙循环延迟。

    b.长延迟
    如果延迟时间比较长,比如秒级延迟,应该基于tick进行延迟。而且应该睡眠。
    例子:
    #include <linux/sched.h>

    /* 睡眠3秒钟 */
    set_current_state(TASK_INTERRUPTIBLE);
    schedule_timeout(3*HZ);


    (2)等待不定时间(直到条件满足为止)的延迟
    这种等待需要使用内核中的等待队列进行延迟。

    等待队列定义在<linux/wait.h>,和要等待的条件一一对应。也就是说,有一个条件,就应该有一个等待队列。

    等待队列要有一个等待队列头:
    wait_queue_head_t xxx;

    例子:
    #include <linux/fs.h>
    #include <linux/wait.h>

    /* 1. 等待队列头 */
    wait_queue_head_t mywait;

    ssize_t mem_write(struct file *filp...)
    {
    /* 如果缓冲区满,则睡眠 */

    //2.不可打断睡眠
    wait_event(mywait, wp!=buf_len);

    //可打断睡眠,如果返回非0,说明是被信号唤醒
    if (wait_event_interruptible(mywait, wp!=buf_len))
    return -ERESTARTSYS;
    ...
    }


    int mem_ioctl(xxx)
    {
    ...
    case MEM_RESET:
    memset(xxx);
    wp = 0;
    /* 3.唤醒整个队列中的进程 */
    wake_up(&mywait);
    或wake_up_interruptible(&mywait);
    ...
    }

    static int __init my_init()
    {
    /* 4.初始化等待队列头 */
    init_waitqueue_head(&mywait);
    ...
    }

    如果不希望唤醒时把整个队列都唤醒,还可以使用exclusive版本的睡眠/唤醒函数。


    4. 内核的定时
    内核的定时器称为timer_list,定义在<linux/timer.h>

    例子:
    struct timer_list mytimer;

    /* 定时器的执行函数 */
    void timer_func(unsigned long data)
    {
    ...
    }


    /* 定时器的初始化 */
    setup_timer(&mytimer, timer_func, data);

    /* 定时器的启动,再未来的给定时间运行 */
    mod_timer(&mytimer, jiffies+xxx);
    也可以用add_timer();

    /* 定时器的删除:一般在模块退出时执行 */
    del_timer(&mytimer);

    day_8

    主题: 1。为什么要用锁以及锁相关的信息
    2。加锁的原则
    3。内核的哪些机制导致必须要加锁
    4。atomic_t(原子变量)
    5。spinlock_t(自旋锁)
    6。mutex(互斥锁)

    1。为啥用锁
    如果内核代码中出现对全局变量的同时访问,那么通常要利用锁来保护。
    临界区(critical region):访问全局变量的代码
    竞争(race):有多个人同时进入临界区,就会发生竞争
    同步(synchronization):如何避免竞争(如何加锁)

    临界区可能分散在多个函数中,大家要共用一把锁。
    临界区的每个部分都应该分别加锁,如果有个别部分忘记加锁,那么锁保护就没意义了。

    锁的唯一作用,就是对数据访问提供保护,不要用锁来完成功能。

    死锁
    自死锁:在获得锁以后,再次要求获得锁
    ABBA死锁:进程需要两把锁时,由于获得顺序问题导致的死锁
    解决方式就是一旦要持有两把锁,必须按给定顺序


    2。加锁原则
    (1)锁是一种自愿的行为,是不得已的,为了保护数据
    (2)锁一定要在程序设计的一开始就考虑,不要程序设计完成后在考虑加锁
    (3)一开始设计锁时可以比较粗糙(加锁范围较大),随着设计深入,可以不断细化临界区(缩小加锁范围)。
    (4)同一临界区的代码可能分散在不同的函数中,用同一把锁来保护,但是临界区每个部分的加锁/解锁函数可能不同。


    3。导致加锁的机制
    (1)SMP
    (2)中断
    (3)内核抢占
    (4)schedule()
    上述内核机制都可能造成同步或者伪同步(虽然只有一个cpu,但利用调度器的调度切换不同进程)

    对临界区的每个部分加锁时,都应该考虑上述4种机制。


    4. atomic_t
    可用atomic_t来替代int型,实现计数的++等功能。由atomic_t的相关函数保证原子性
    例子:
    #include <linux/atomic.h>
    static atomic_t i = ATOMIC_INIT(0);

    /* 计数++ */
    atomic_inc(&i);

    参考arm-drv/atomic_test01.c


    5. spinlock_t
    特征:
    (1)临界区1个人
    (2)等待锁的人通过忙循环等待
    (3)持有锁的人不能睡眠
    spinlock锁是内核中最常用的锁,持有时间通常是ns级的。

    结合内核的机制,spinlock锁的适用范围:
    SMP,中断以及内核抢占。

    如果临界区中必须睡眠(调用schedule),则该临界区不能用spinlock提供保护。

    参考arm-drv/spin_test01.c


    6. mutex / semaphore
    特征:
    (1)临界区里一个人
    (2)等待锁的人睡眠等
    (3)持有锁时可以睡眠(必须能被唤醒)
    mutex适用于临界区中可能睡眠的情况,在内核中用的比较多,持有锁的时间常常是ms级。

    mutex可以针对的内核机制有:
    SMP,内核抢占,schedule()。
    如果临界区中包含中断处理,则无法用mutex进行保护(因为获取mutex时可能睡眠,而中断中不能睡)

    例子:
    #include <linux/mutex.h>

    //声明锁
    struct mutex mylock;

    ssize_t xxx_write()
    {
    int ret;
    //加锁
    mutex_lock(&mylock);
    或可被信号打断的版本:
    ret = mutex_lock_interruptible(&mylock);
    if (ret)
    return -ERESTARTSYS;
    ...
    //解锁
    mutex_unlock(&mylock);

    }

    xxx_init(void)
    {
    ...
    //锁的初始化
    mutex_init(&mylock);
    ...
    }

    day_9

    主题: 1。基于mutex实现PIPE的例子
    2。spinlock/mutex的一些相关变种锁
    3。设备模型的概述
    4。设备模型的核心结构体
    5。基于设备模型改造写好的char驱动
    6。基于设备模型实现设备文件的自动创建(自动mknod)

    1.pipe的例子
    参考x86-drv/06mutex/mutex_test01.c

    2.变种锁
    (1)rwlock_t
    例子:
    #include <linux/spinlock.h>

    rwlock_t mylock;

    /* 在只读的临界区,用读函数加锁 */
    read_lock(&mylock);
    ...
    read_unlock(&mylock);

    /* 如果临界区可读写,则加写锁 */
    write_lock(&mylock);
    ...
    write_unlock(&mylock);


    (2)seqlock_t
    顺序锁。可以避免写者饥饿。但是,要求临界区中不能涉及指针操作


    (3)semaphore(信号量)
    semaphore的出现时间早于mutex。如果semaphore中的计数为1,则使用方式同mutex。
    目前的内核已经不再使用semaphore保护临界区,只用semaphore限制对有限资源的访问人数。


    3. 设备模型概述
    最早期的设备模型用于解决电源管理方面的问题。其实就是在内核中创建了若干个和实际设备一一对应的结构体。结构体中记录设备的信息。
    随着设备模型的发展,内核为用户态观察设备的信息提供了接口。
    即/sys目录


    4. 核心结构体
    内核在设计设备模型时,引入了面向对象的思想。所以,相关结构体按照对象设计。一般会有3层。


    (1)<linux/kobject.h>
    kobject.h中是最核心的结构体,包括:
    kobject:对应一个对象
    kset: 对应一个容器

    (2)<linux/device.h>
    device.h中的类是kobject/kset的子类。用于描述设备,驱动等。
    a.device
    继承于kobject,对设备信息的抽象,和实际的设备一一对应
    b.device_driver
    继承于kobject,和设备驱动一一对应
    c.class
    继承于kset,内核中通常将同一类型的多个设备/驱动等放到给定的class中
    d.bus_type
    继承于kset,内核将同一中总线下的设备/驱动放置到给定的bus_type中


    (3)<linux/platform_device.h>
    对于6410内部的大量设备来说,采用的总线为platform总线。因此,每个实际存在的设备应该创建一个platform_device结构体。
    驱动人员为设备开发的驱动,应该用platform_driver封装。

    platform_device
    对应一个硬件。以6410为例,驱动开发人员或者移植人员应该为6410中每个实际存在的硬件创建一个platform_device。用硬件的信息,比如物理地址,中断号等来初始化platform_device
    比如6410有4个UART,那么需要创建4个platform_device

    由于开发板上实际用到的设备通常少于6410中存在的设备,因此,内核要求将实际用到设备的pdev注册到内核。

    对于6410来说,已经准备好的platform_device分步在如下目录:
    (1).../arch/arm/plat-s3c/
    其中的dev-ts.c等
    (2).../arch/arm/plat-s3c64xx/
    其中的devs.c等

    对于使用的开发板来说,需要把真正用到硬件的platform_device注册到内核,SMDK6410开发板将这些pdev的指针集中到一起,即:
    文件arch/arm/mach-s3c6410/mach-smdk6410.c
    中的数组smdk6410_devices。
    当内核启动时,会自动将数组中的每个platform_device注册到内核中。


    platform_driver


    kobject --> device --> platform_device
    --> device_driver --> platform_driver

    kset --> class
    --> bus_type

    day_10

    主题: 1。platform_device/platform_driver的例子
    2。自动创建设备文件的例子
    3。linux驱动中的子系统
    4。input子系统

    1.
    参考x86-drv/06model/plat_dev1.c, plat_dev2.c, plat_drv1.c

    2.自动创建设备文件
    参考plat_drv2.c


    3. 子系统
    子系统的优点:
    (1)简化驱动的设计
    (2)给用户态一个统一的接口
    (3)提高代码的复用率
    在现在的内核中,基本上每一类相似的硬件,都会有对应的子系统。比如支持所有flash设备的子系统在drivers/mtd目录下;支持所有输入设备(鼠标,键盘等)的子系统在drivers/input目录下;

    子系统的缺点:
    (1)由于每类设备都有自己的子系统,所以要写驱动,必须先了解对应的子系统
    由于子系统相关资料比较缺乏,所以,详细的子系统设计需要以代码为准。

    4. input子系统
    实现文件可参考drivers/input/目录下的input.c以及evdev.c
    input子系统的核心头文件是include/linux/input.h

     day_11

    主题: 1。input子系统的核心结构体
    2。input硬件之触摸屏
    3。input驱动的platform_device和platform_driver
    4。input驱动的设计

    作业:
    1。看完input驱动,重点看两个中断处理函数和一个定时器函数
    2。看DM9000手册的P13~27

    1.核心结构体
    定义在<linux/input.h>
    基本上,每个子系统都会在include/linux目录下有一个核心的头文件,其中定义了子系统的核心结构体。

    要查看当前系统中已经存在的input设备,可以参考文件:
    /proc/bus/input/devices

    (1)input_event
    结构体占16个字节,每当设备产生按键,坐标等信息时,input驱动将设备的事件封装到input_event中,然后通报给input子系统

    (2)input_dev
    在input子系统中定义,用于代表一个实际的input设备。
    input_dev由input驱动分配并初始化,然后注册到input子系统中。input驱动应该为每个实际存在的设备都分配并注册一个对应的input_dev。


    2。TS硬件


    3。ADC的platform_device和platform_driver
    和触屏相关的platform_device应该已经由三星准备好了,放在arch/arm/plat-s3c/dev-ts.c中

    由于platform_device只能记录物理地址,中断号等通用信息,很多驱动在准备pdev时还会分配一个设备独有的结构体,用于记录设备的独特信息。比如,三星就为触摸屏设备设定了结构体s3c_ts_mach_info。

    该结构体和platform_device一起准备,只会由三星触摸屏的驱动使用。

    该结构体通常在arch/arm/mach-s3c6410/mach-smdk6410.c中准备,里面记录触摸屏独有的信息。


    platform_driver来自于drivers/input/touchscreen/s3c-ts.c
    将其拷贝到drivers/arm-drv/目录下

    三星的移植人员还要为6410中的每个硬件准备一个clk结构体,记录该硬件的时钟信息。
    这些clk结构体集中在arch/arm/plat-s3c64xx/clock.c中

    驱动用clk_get()找到给定设备的clk结构体;
    然后用clk_enable()使能该设备的时钟

     day_12

    主题: 1。input驱动的补完
    2。input驱动的测试
    3。DM9000网卡的platform_device
    4。DM9000网卡的platform_driver
    5。网卡驱动的核心结构体net_device

    作业:1。基于开发板的按键写出标准的input驱动


    1.
    参考ts_test01.c


    2.测试


    3。DM9000的platform_device
    自行实现platform_device
    参考arm-drv/dm9000_dev.c

    4. DM9000的platform_driver
    MII: Media Indepedent Interface(媒体无关接口)
    MII是MAC和PHY之间的接口


    5. net_device
    定义在<linux/netdevice.h>
    是网络驱动的核心结构体。

    net_device->name: 设备名字,如eth0等
    net_device->dev_addr: 设备的MAC地址

    net_device必须提供的3个函数是:
    hard_start_xmit: 完成数据包的发送
    open: 和用户态用ifconfig配置IP地址的行为对应
    $>ifup eth0
    stop: 用$>ifdown eth0对应

    day_13

    主题: 1。DM9000驱动的platform_driver
    2。net_device->open|stop|hard_start_xmit
    3。网卡驱动的中断处理

    作业:
    1。复习DM9000驱动
    2。看6410手册的Display Controller一章

    1。platform_driver
    参考arm-drv/dm9000_test01.c

    day_14

    主题: 1。6410的Display Controller(FIMD)和LCD屏
    2。framebuffer驱动的架构
    3。针对6410 FIMD的fb驱动
    4。驱动的测试

    day_15

    主题: 1。块设备的特征
    2。块设备驱动和块调度层
    3。块设备的核心结构体
    4。在x86上的一个块设备驱动例子

    1/2

    3。核心结构体
    (1)gendisk
    定义在<linux/genhd.h>
    和一个实际存在的磁盘对应。由驱动分配并初始化,和磁盘一一对应。

    (2)hd_struct
    定义在<linux/genhd.h>
    和一个磁盘上的分区对应。记录分区的起始扇区,扇区数,分区的一些统计信息等

    (3)request
    定义在<linux/blkdev.h>
    和一组连续扇区的访问对应。request由块调度层产生,提交给块设备驱动,每个request要么读,要么写。驱动得到request后,应该将磁盘上对应扇区的信息拷贝到request对应的缓冲区或相反。


    (4)request_queue
    定义在<linux/blkdev.h>
    request的队列。块调度层生成的请求首先要挂载到request队列中,然后在提交给块驱动。

    (5)block_device_operations
    定义在<linux/blkdev.h>
    一组访问函数集,由驱动提供,当用户态需要对/dev下的设备文件进行mount,fdisk等访问时,需要调用其中的函数。
    一旦分区mount成功,对分区文件的读写不会调用block_device_operations中的函数。

  • 相关阅读:
    20169221 2016-2017-2 《移动平台应用开发实践》第十一周学习总结
    20169221 2016-2017-2 《移动平台应用与开发》第十周实验总结
    20169201 2016-2017-2 《网络攻防实践》实验三 缓冲区溢出实验
    20169218 2016-2017-2 《网络攻防实践》第九周作业
    2016-2017 《移动平台开发》实验三 敏捷开发与XP实践
    20169221 2016-2017-2 实验一 网络攻防环境的搭建与测试
    实验二《Java面向对象》实验报告
    20169221 2016-2017-2 《移动平台开发》第七周学习总结
    20169221 2016-2017-2 《网络攻防》第七周学习总结
    20169221 2016-2017-2 《移动平台开发》第六周学习总结
  • 原文地址:https://www.cnblogs.com/Huluwa-Vs-Aoteman/p/3453054.html
Copyright © 2011-2022 走看看