精华内容
下载资源
问答
  • ARM LINUX学习博文

    2020-03-19 17:17:58
    ARM LINUX学习博文: https://blog.csdn.net/yhb1047818384?t=1 ARM aarch64汇编学习笔记(一):ARMv8架构 https://blog.csdn.net/myond/article/details/85239801 ARM64的启动过程之(六):异常向量表的设定 ...

    ARM LINUX学习博文:
    https://blog.csdn.net/yhb1047818384?t=1

    ARM aarch64汇编学习笔记(一):ARMv8架构
    https://blog.csdn.net/myond/article/details/85239801

    https://blog.csdn.net/qq_42826337/article/details/104371349

    https://blog.csdn.net/tanli20090506/article/details/71487570?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

    https://blog.csdn.net/tanli20090506/article/details/71435777?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

    https://blog.csdn.net/forever_2015/article/details/50285865

    https://blog.csdn.net/tanli20090506/article/details/71487570?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

    https://blog.csdn.net/yhb1047818384/article/details/81150288

    https://www.veryarm.com/tag/arm/page/73
    ARM64的启动过程之(六):异常向量表的设定
    https://www.veryarm.com/65657.html

    展开全文
  • I.MX6ULL ARM Linux学习笔记

    千次阅读 2020-02-21 10:06:01
    I.MX6ULL ARM Linux学习笔记写在前面 写在前面 本文章为我在学习I.MX6ULL ARM Linux时的记录文章,知识来源为《正点原子阿尔法 I.MX6ULL ARM Linux开发板》的视频教程和野火《i.MX Linux开发实战指南》的视频和电子...

    写在前面

    本文章为我在学习I.MX6ULL ARM Linux时的记录文章,知识来源为《正点原子阿尔法 I.MX6ULL ARM Linux开发板》的视频教程和野火《i.MX Linux开发实战指南》的视频和电子书籍。

    学习笔记涉及在学习过程中有关安装,使用,编程,debug的相关知识点和技巧,在一套教程学习完毕后,我将再次整理本笔记,将其中重要的坑点,知识点和一些技巧和问题解决等整理成专栏,敬请大家静待花开,谢谢!

    S1:U-Boot

    在这里插入图片描述

    U-Boot启动界面:(串口)
    启动时3秒内按Enter进入U-Boot命令行模式。
    在这里插入图片描述
    注意:
    1.ARM没有PC机中的BIOS,Uboot就类似于BIOS+系统引导程序
    2.U-Boot 最重要的功能就是初始化DDR,并将linux镜像和dts设备数拷贝到内存中。
    3.大多数的ARM在引导linux之前都需要U-Boot初始化DDR,但有一些ARM的bootROM(就比如I.MX6ULL)会自动初始化DDR,无需U-Boot再初始化了。

    S2:正点原子U-Boot编译

    在这里插入图片描述
    在这里插入图片描述

    也可以直接在MakeFile中写好ARCH和CROSS_COMPILE:
    在这里插入图片描述

    S3:U-Boot基本命令第1讲:help、bdinfo和环境变量命令

    1.help命令:?+命令名
    例子:

    ? boot
    

    2.信息查看命令:板子信息

     bdinfo
    

    3.与环境变量有关的命令:

    1.setenv(重点):增,删,改环境变量信息
    (注意 字符串和带空格的要加单引号把整个串括起来,单纯数字不用引号)
    举例:
    新增一个名为author 的环境变量,值为whstudio并保存

    setenv author 'whstudio'
    saveenv
    

    删除一个名为author 的环境变量并保存(将值置为空就是删除)

    setenv author
    saveenv
    

    修改一个名为author 的环境变量,值为whstudio并保存(环境变量存在就是修改)

    setenv author 'whstudio'
    saveenv
    

    2.printenv(重点):查环境变量信息
    举例:
    查看所有环境变量:

    printenv
    

    S4:U-Boot基本命令第2讲:内存操作命令

    内存操作命令(阅读说明1):

    命令 用途 格式 举例 说明
    md 显示内存值 md[.b, .w, .l] address [# of objects] md.b 80000000 14 0X80000000 开始的 20 个字节的内存值
    nm 修改指定地址的内存值 nm [.b, .w, .l] address nm.l 80000000 以.l 格式修改 0x80000000 地址的数据为 0x12345678(阅读说明2)
    mm 修改指定地址内存值的,使用 mm 修改内存值的时候地址会自增 mm [.b, .w, .l] address mm.l 80000000 以.l 格式修改 0x80000000 为起始地址的数据
    mw 使用一个指定的数据填充一段内存 mw [.b, .w, .l] address value [count] mw.l 80000000 0A0A0A0A 10 使用.l 格式将以 0X80000000 为起始地址的 0x10 个内存块(0x10 * 4=64 字节)填充为 0X0A0A0A0A
    cp 数据拷贝命令 cp [.b, .w, .l] source target count cp.l 80000000 80000100 10 使用.l 格式将 0x80000000 处的地址拷贝到 0X80000100 处,长度为 0x10 个内存块(0x10 * 4=64 个字节)
    cmp 比较命令,用于比较两段内存的数据是否相等 cmp [.b, .w, .l] addr1 addr2 count cmp.l 80000000 80000100 10 使用.l 格式来比较 0x80000000 和 0X80000100 这两个地址数据是否相等,比较长度为 0x10 个内存块(16 * 4=64 个字节)

    说明1:
    命令中的[.b .w .l]对应 byte、word 和 long,也就是分别以 1 个字节、2 个字节、4 个字节来显示内存值。address 就是要查看的内存起始地址,[# of objects]表示要查看的数据长度,这个数据长度单位不是字节,而是跟你所选择的显示格式有关。比如你设置要查看的内存长度为20(十六进制为 0x14),如果显示格式为.b 的话那就表示 20 个字节;如果显示格式为.w 的话就表示 20 个 word,也就是 202=40 个字节;如果显示格式为.l 的话就表示 20 个 long,也就是204=80 个字节。另外要注意:
    uboot 命令中的数字都是十六进制的!不是十进制的!

    说明2:
    输入上述命令以后如图 30.4.3.2 所示:
    在这里插入图片描述
    图 30.4.3.2 nm 命令
    在图 30.4.3.2 中,80000000 表示现在要修改的内存地址,ffffff00 表示地址 0x80000000 现在的数据,?后面就可以输入要修改后的数据 0x12345678,输入完成以后按下回车,然后再输入‘q’即可退出,如图 30.4.3.3 所示:
    在这里插入图片描述
    图 30.4.3.3 修改内存数据
    修改完成以后在使用命令 md 来查看一下有没有修改成功,如图 30.4.3.4 所示:
    图 30.4.3.4 查看修改后的值
    在这里插入图片描述
    从图 30.4.3.4 可以看出,此时地址 0X80000000 的值变为了 0x12345678。

    S5:U-Boot基本命令第3讲:网络操作命令

    要使用网络首先要配置一些环境变量:

    环境变量 描述
    ipaddr 开发板 ip 地址,可以不设置,使用 dhcp 命令来从路由器获取 IP 地址。
    ethaddr 开发板的 MAC 地址,一定要设置。
    gatewayip 网关地址。
    netmask 子网掩码。
    serverip 服务器 IP 地址,也就是 Ubuntu 主机 IP 地址,用于调试代码。

    例如:

    setenv ipaddr 192.168.1.50 
    setenv ethaddr 00:04:9f:04:d2:35 
    setenv gatewayip 192.168.1.1
    setenv netmask 255.255.255.0
    setenv serverip 192.168.1.250 
    saveenv
    

    注意:
    网络地址环境变量的设置要根据自己的实际情况,确保 Ubuntu 主机和开发板的 IP 地址在同一个网段内。

    比如我现在的开发板和电脑都在 192.168.1.0 这个网段内,所以设置开发板的 IP 地址为 192.168.1.50,我的 Ubuntu 主机的地址为 192.168.1.250,因此 serverip 就是192.168.1.250。

    ethaddr 为网络 MAC 地址,是一个 48bit 的地址,如果在同一个网段内有多个开发板的话一定要保证每个开发板的 ethaddr 是不同的,否则通信会有问题!设置好网络相关的环境变量以后就可以使用网络相关命令了。

    命令 用途 格式 举例 说明
    ping 是否可以与指定ip进行通信 ping ip ping 192.168.1.250 注意!只能在 uboot 中 ping 其他的机器,其他机器不能 ping uboot,因为 uboot 没有对 ping命令做处理,如果用其他的机器 ping uboot 的话会失败!
    dhcp 用于从路由器获取 IP 地址 dhcp dhcp 直接输入 dhcp 命令即可通过路由器获取到 IP 地址
    nfs 通过 nfs 可以在计算机之间通过网络来分享资源 nfs [loadAddress] [[hostIPaddr:]bootfilename] nfs 80800000 192.168.1.250:/home/zuozhongkai/linux/nfs/zImage 使用 nfs 命令来将 zImage 下载到开发板 DRAM 的 0X80800000 地址处 (ubuntu系统上要建立并开启nfs服务)
    tftp 通过网络下载东西到 DRAM 中 tftpboot [loadAddress] [[hostIPaddr:]bootfilename] tftp 80800000 zImage 将 tftpboot 文件夹里面的 zImage 文件下载到开发板 DRAM 的 0X80800000 地址处 (ubuntu系统上要建立并开启tftp服务)

    注意点1:
    nfs 命令的区别在于,tftp 命令不需要输入文件在 Ubuntu 中的完整路径,只需要输入文件名即可。

    注意点2:
    在这里插入图片描述
    在图 中可以看到“TFTP error: ‘Permission denied’ (0)”这样的错误提示,提示没有权限,出现这个错误一般有两个原因:
    ①、在 Ubuntu 中创建 tftpboot 目录的时候没有给予 tftboot 相应的权限。
    ②、tftpboot 目录中要下载的文件没有给予相应的权限。
    针对上述两个问题,使用命令“chmod 777 xxx”来给予权限,其中“xxx”就是要给予权限的文件或文件夹。

    S6:U-Boot基本命令第4讲:FAT操作命令

    S7:U-Boot基本命令第5讲:boot相关操作命令

    S8:ubuntu下交叉编译工具与传输环境搭建

    安装VScode
    VScode其实在linux下可以直接运行,不需要安装
    我们要做的是给文件夹找一个合适的位置,然后把开始菜单和桌面的程序图标加进去

    第一步:下载VScode
    在Visual Studio Code官网下载:
    VScode linux 64位

    第二步:解压缩文件

    tar -xvzf code-stable-1536736541.tar.gz
    

    第三步:移动到/usr/local/ 目录,给可执行的权限, 然后就已经可以运行了

    mv VSCode-linux-x64 /usr/local/
    chmod +x /usr/local/VSCode-linux-x64/code
    

    第四步:添加到开始菜单和桌面
    复制一个VScode图标文件到 /usr/share/icons/ 目录(后面会有用),并且编辑VSCode.desktop

    cp /usr/local/VSCode-linux-x64/resources/app/resources/linux/code.png /usr/share/icons/
    vim /usr/share/applications/VSCode.desktop
    

    然后这样写:

    [Desktop Entry]
    Name=Visual Studio Code
    Comment=Multi-platform code editor for Linux
    Exec=/usr/local/VSCode-linux-x64/code
    Icon=/usr/share/icons/code.png
    Type=Application
    StartupNotify=true
    Categories=TextEditor;Development;Utility;
    MimeType=text/plain;
    

    保存退出,在开始菜单里面应该就看到VScode了。
    如果想要桌面上也存在,就这样:

    cp /usr/share/applications/VSCode.desktop ~/桌面位置
    

    安装交叉编译器

    通常编译工具链对编译环境有较高的要求,编译复杂的程序时,可能需要巨大的存储空间以及强大的 CPU 运算能力加快编译速度。常见的ARM 架构平台资源有限,无论是存储空间还是 CPU 运算能力,都与 PC 相去甚远,特别是对于 MCU 平台,安装编译器根本无从谈起。有了交叉编译,我们就可以在 PC 上快速编译出针对其他架构的可执行程序。

    安装过程非常简单,apt安装即可:

    sudo apt install gcc-arm-linux-gnueabihf
    

    安装NFS server和挂载NFS目录

    第零步:ubuntu和板子处于一个路由器内,并相互ping成功

    第一步:虚拟机设置,实体机请跳过:

    网络连接选择桥接并勾选复制物理网络连接状态:
    在这里插入图片描述
    笔记本中经常会有两个或者两个以上的网卡,根据需要进行如下设置:
    在这里插入图片描述
    再点击管理员模式:
    在这里插入图片描述
    在里面选择直通(近似可以这样理解)的网卡即可。

    总之,大家的电脑和虚拟机不同,不管如何折腾或者怎么连接,目的就是虚拟机和板子之间可以互相Ping成功
    ubuntu ping 板子:
    在这里插入图片描述

    板子ping ubuntu :
    在这里插入图片描述

    第二步:安装NFS server和挂载NFS目录

    apt安装即可:

    sudo apt-get install nfs-kernel-server
    

    查看id:

    id
    

    记下uid(用户id) 和gid(组id),接下来要用。

    安装 NFS 服务后,会新增一个/etc/exports 文件(即/etc 目录下名字为 exports 的文件),
    NFS 服务根据它的配置来运行。
    这里我们直接使用shell命令修改一下:

    sudo echo "/home/pibot/nfs  *(rw,sync,all_squash,anonuid=????,anongid=????,no_subtree_check)" > /etc/exports
    sudo exportfs -arv
    

    别忘了把uid和gid填入问号里面去

    这里面的*代表所有ip均可以访问,当然也可以指定ip,把它换成:ip/24即可
    把/home/pibot/nfs换成你想要挂上去的文件夹

    板子设置:

    mkdir /home/root/mountnfs
    mount -o vers=4 192.168.0.128:/home/pibot/nfs /home/root/mountnfs
    
    
    

    执行过去了,就基本正常了,再去挂载目录下看

    在这里插入图片描述
    文件过来了,搞定!

    展开全文
  • ARM Linux学习总结

    2013-04-27 09:29:43
    ARM Linux特指运行于ARM架构处理器平台的Linux,区别于运行于PC上的X86 LinuxARM架构处理器多用在嵌入式系统中,通常被集成到片上系统(SoC)。由于片上系统整合外部设备控制器的多样性,对于任何一片特定的片上...

    ARM Linux特指运行于ARM架构处理器平台的Linux,区别于运行于PC上的X86 Linux。

    ARM架构处理器多用在嵌入式系统中,通常被集成到片上系统(SoC)。由于片上系统整合外部设备控制器的多样性,对于任何一片特定的片上系统,需要对ARM Linux进行定制,以使集成在片上系统中的各项功能可用,这个过程称为ARM Linux移植。

    ARM Linux移植的主要工作:1)Bootloader移植;2)ARM Linux内核移植;3)根文件系统制作。完成这三项基本工作之后,就可以在运行于片上系统之上的ARM Linux基础上开发针对特定需求的应用,包括推迟到开发阶段的驱动程序、应用程序等。 

    1)Bootloader移植。

    ARM Linux内核被设计为尽可能平台无关,而Bootloader则负责提供板级支持包(BSP)的功能,在为ARM Linux内核提供基本的运行环境之后,如CPU可用、内存可用,便可加载ARM Linux内核,并将系统控制权完全交给ARM Linux内核。这个过程称为引导ARM Linux内核。一般来讲,Bootloader是特定于硬件平台的,但是由于某些开源Bootloader,如UBOOT的存在,使得开发人员可以对既有Bootloader进行尽可能少量的修改,便可将其用来在特定目标板上引导ARM Linux内核。

    UBOOT引导ARM Linux内核的一般流程:初始化CPU环境,如关中断、设定运行模式 --> 初始化DRAM控制器,以使内存可用 --> 如有必要,初始化串口用于输出引导信息,初始化网口用于下载ARM Linux内核镜像 --> 加载ARM Linux内核镜像 --> 为ARM Linux内核准备参数,将控制流转到ARM Linux内核入口。

    2)ARM Linux内核移植。

    ARM Linux内核接受Bootloader的引导参数,如DRAM大小和起始地址、用作Console的设备的名称、根文件系统的位置。为了使ARM Linux内核能够正常使用片上系统芯片内部及目标板上的各种功能,在这个过程中需要在ARM Linux内核代码中加入平台特定的一些代码,通常将这些代码存放在arch/arm目录下的某个机器特定的目录中。ARM Linux内核中已经包含了大量平台特定的代码,但相比平台的多样性,这些代码还是不够的。 在编译生成ARM Linux内核镜像之前,需要对ARM Linux内核进行必要的配置,使在内核代码中实现的某项功能被加入或不加入ARM Linux内核,如加入对mini2440目标板和smdk2440目标板的支持、加入针对三星SoC串口的驱动、不加入FAT文件系统。需要说明一下,ARM Linux内核可以同时支持多种目标板(或称机器类型),但在启动的时候只会根据Bootloader在R1寄存器中指定的机器码来选定一款支持的机器类型,在执行平台无关代码之前进行与选定机器类型平台相关的初始化工作。在平台相关的初始化工作中可以将目标板上的各种功能都初始化,也可什么都不做,而将这些工作推迟到开发阶段来完成。

    3)根文件系统制作。

    如果ARM Linux内核启动后不需要访问文件系统,就不需要做这项工作。但是大多数情况下,ARM Linux内核在启动后会从文件系统中读取配置文件,并根据配置文件中的内容完成更多的工作,如启动必要的工作进程,使系统进入到指定的工作状态。在ARM Linux内核的启动参数中,initrd指定ARM Linux内核启动后需要执行的程序,ARM Linux内核从指定的根文件系统中加载这个程序并用其创建init进程。init进程做什么工作则是由这个指定的程序来决定的。可能的工作,如启动一个Shell程序或者打开一个图形用户界面。BusyBox提供了一个用于此目的的程序,名称为linuxrc,可以将linuxrc配置为启动一个类似于Linux中的bash的Shell程序,BusyBox同样提供了这个Shell程序。

    展开全文
  • armlinux学习笔记--触摸屏驱动程序分析//*******************************************************//*2007.6.26//*******************************************************Linux 下的触摸屏驱动程序主要都在/...

    armlinux学习笔记--触摸屏驱动程序分析

    //*******************************************************

    //* 2007.6.26

    //*******************************************************

    Linux 下的触摸屏驱动程序主要都在/kernel/drivers/char/s3c2410-ts.c 文件中。

    触摸屏的file_operations 结构定义如下:

    static struct file_operations s3c2410_fops = {

    owner: THIS_MODULE,

    open: s3c2410_ts_open,

    read: s3c2410_ts_read,

    release: s3c2410_ts_release,

    #ifdef USE_ASYNC

    fasync: s3c2410_ts_fasync,

    #endif

    poll: s3c2410_ts_poll,

    };

    在触摸屏设备驱动程序中,全局变量struct TS_DEV tsdev 是很重要的,用来保存触摸屏的相关参数、等待处理的消息队列、当前采样数据、上一次采样数据等信息,数据结构struct TS_DEV 的定义如下:

    typedef struct {

    unsigned int penStatus; /* PEN_UP, PEN_DOWN, PEN_SAMPLE */

    TS_RET buf[MAX_TS_BUF]; /* protect against overrun(环形缓冲区) */

    unsigned int head, tail;/* head and tail for queued events (环形缓冲区的头尾)*/

    wait_queue_head_t wq; //* 等待队列数据结构

    spinlock_t lock; //* 自旋锁

    #ifdef USE_ASYNC

    struct fasync_struct *aq;

    #endif

    #ifdef CONFIG_PM

    struct pm_dev *pm_dev;

    #endif

    } TS_DEV;

    static TS_DEV tsdev;

    在/kernel/include/asm-arm/linuette_ioctl.h 文件中:

    typedef struct {

    unsigned short pressure; //* 压力,这里可定义为笔按下,笔抬起,笔拖曳

    unsigned short x;  //* 横坐标的采样值

    unsigned short y;  //* 纵坐标的采样值

    unsigned short pad;  //* 填充位

    } TS_RET;

    在/kernel/include/linux/wait.h 文件中:

    struct __wait_queue_head {

    wq_lock_t lock;

    struct list_head task_list;

    #if WAITQUEUE_DEBUG

    long __magic;

    long __creator;

    #endif

    };

    typedef struct __wait_queue_head wait_queue_head_t;

    TS_RET结构体中的信息就是驱动程序提供给上层应用程序使用的信息,用来存储触摸屏的返回值。上层应用程序通过读接口,从底层驱动中读取信息,并根据得到的值进行其他方面的操作。

    TS_DEV结构用于记录触摸屏运行的各种状态,PenStatus包括PEN_UP、PEN_DOWN和PEN_FLEETING。 buf[MAX_TS_BUF]是用来存放数据信息的事件队列,head、tail分别指向事件队列的头和尾。程序中的笔事件队列是一个环形结构,当有事 件加入时,队列头加一,当有事件被取走时,队列尾加一,当头尾位置指针一致时读取笔事件的信息,进程会被安排进入睡眠。wq等待队列,包含一个锁变量和一 个正在睡眠进程链表。当有好几个进程都在等待某件事时,Linux会把这些进程记录到这个等待队列。它的作用是当没有笔触事件发生时,阻塞上层的读操作, 直到有笔触事件发生。lock使用自旋锁,自旋锁是基于共享变量来工作的,函数可以通过给某个变量设置一个特殊值来获得锁。而其他需要锁的函数则会循环查 询锁是否可用。MAX_TS_BUF的值为16,即在没有被读取之前,系统缓冲区中最多可以存放16个笔触数据信息。(关于自旋锁的作用和概念可以参考一 篇《Linux内核的同步机制》文章的相关章节。)

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

    模块初始化函数是调用s3c2410_ts_init 函数来实现的,主要完成触摸屏设备的内核模块加载、初始化系统I/O、中断注册、设备注册,为设备文件系统创建入口等标准的字符设备初始化工作。

    static int __init s3c2410_ts_init(void)

    ret = register_chrdev(0, DEVICE_NAME, &s3c2410_fops);

    tsMajor = ret;

    这里首先对字符设备进行注册,将触摸屏的file_operations 结构中的函数接口传入内核,注册成功后获得系统自动分配的主设备号。

    set_gpio_ctrl(GPIO_YPON);

    set_gpio_ctrl(GPIO_YMON);

    set_gpio_ctrl(GPIO_XPON);

    set_gpio_ctrl(GPIO_XMON);

    在/kernel/include/asm-arm/arch-s3c2410/smdk.h 文件中:

    #define GPIO_YPON (GPIO_MODE_nYPON | GPIO_PULLUP_DIS | GPIO_G15)

    #define GPIO_YMON (GPIO_MODE_YMON | GPIO_PULLUP_EN | GPIO_G14)

    #define GPIO_XPON (GPIO_MODE_nXPON | GPIO_PULLUP_DIS | GPIO_G13)

    #define GPIO_XMON (GPIO_MODE_XMON | GPIO_PULLUP_EN | GPIO_G12)

    GPIO 口的Port G 端口有4个管脚对应触摸屏的控制接口,分别是:

    GPG15 --- nYPON  Y+ 控制信号

    GPG14 --- YMON   Y- 控制信号

    GPG13 --- nXPON  X+ 控制信号

    GPG12 --- XMON   X- 控制信号

    需要通过配置GPGCON 寄存器来设定该端口管脚的输出模式,对应位如下:

    [31:30] [29:28] [27:26] [25:24]

    GPG15   GPG14   GPG13   GPG12   ...

    参考S3C2410 芯片datasheet 的I/O口章节,都要设为11(二进制)。

    ADCDLY = 30000;

    配置ADCDLY 寄存器,对自动转换模式来说是设定ADC 开始转换时的延时时间,以及对X轴和Y轴转换的时间间隔,对正常模式来说仅设定对X轴和Y轴转换的时间间隔。注意该值不能为0。

    在/kernel/arch/arm/kernel/irq.c 文件中:

    /**

    * request_irq - allocate an interrupt line

    * @irq: Interrupt line to allocate

    * @handler: Function to be called when the IRQ occurs

    * @irqflags: Interrupt type flags

    * @devname: An ascii name for the claiming device

    * @dev_id: A cookie passed back to the handler function

    *

    * This call allocates interrupt resources and enables the

    * interrupt line and IRQ handling. From the point this

    * call is made your handler function may be invoked. Since

    * your handler function must clear any interrupt the board

    * raises, you must take care both to initialise your hardware

    * and to set up the interrupt handler in the right order.

    *

    * Dev_id must be globally unique. Normally the address of the

    * device data structure is used as the cookie. Since the handler

    * receives this value it makes sense to use it.

    *

    * If your interrupt is shared you must pass a non NULL dev_id

    * as this is required when freeing the interrupt.

    *

    * Flags:

    *

    * SA_SHIRQ  Interrupt is shared

    *

    * SA_INTERRUPT  Disable local interrupts while processing

    *

    * SA_SAMPLE_RANDOM The interrupt can be used for entropy

    *

    */

    int request_irq(unsigned int irq, void (*handler)(int, void *, struct pt_regs *),

    unsigned long irq_flags, const char * devname, void *dev_id)

    其中handler 函数指针的具体参数为:

    void (*handler)(int irq, void *dev_id, struct pt_regs *regs)

    函数request_irq 是Linux 系统中驱动程序注册中断的方法。irq 为所要申请的硬件中断号,handler 为系统所注册的中断处理子程序,irq_flags 为申请时的选项,devname 为指向设备名称的字符指针,dev_id 为申请时告诉系统的设备标识。若中断申请成功则返回0,失败则返回负值。

    ret = request_irq(IRQ_ADC_DONE, s3c2410_isr_adc, SA_INTERRUPT,

    DEVICE_NAME, s3c2410_isr_adc);

    调用该函数来进行A/D转换的中断注册,所要申请的硬件中断号为IRQ_ADC_DONE(62);系统所注册的中断处理子程序为 s3c2410_isr_adc 函数;申请中断选项为SA_INTERRUPT,表示中断处理程序是快速处理程序,即快速处理程序运行时,所有中断都被屏蔽;设备名称定义为 DEVICE_NAME,即"s3c2410-ts";而设备标识仍然用中断处理子程序代替。

    ret = request_irq(IRQ_TC, s3c2410_isr_tc, SA_INTERRUPT,

    DEVICE_NAME, s3c2410_isr_tc);

    接着继续调用该函数来进行触摸屏触摸的中断注册,所要申请的硬件中断号为IRQ_TC(61);系统所注册的中断处理子程序为 s3c2410_isr_tc 函数;申请中断选项为SA_INTERRUPT,表示中断处理程序是快速处理程序,即快速处理程序运行时,所有中断都被屏蔽;设备名称定义为 DEVICE_NAME,即"s3c2410-ts";而设备标识仍然用中断处理子程序代替。

    /* Wait for touch screen interrupts */

    wait_down_int();

    调用该宏函数来设置触摸屏为等待中断模式【笔按下产生中断】,具体定义如下:

    #define wait_down_int() { ADCTSC = DOWN_INT | XP_PULL_UP_EN | "

    XP_AIN | XM_HIZ | YP_AIN | YM_GND | "

    XP_PST(WAIT_INT_MODE); }

    用该宏函数来设置ADC 触摸屏控制寄存器,参考S3C2410 芯片datasheet 中关于触摸屏的章节,具体设置参数如下:

    DOWN_INT = 1<<8 * 0  该位保留且应该设为0 【笔按下或笔抬起中断信号控制位,设为0 表示笔按下产生中断信号】

    XP_PULL_UP_EN = 1<<3 * 0  上拉开关使能,设为0 表示XP 引脚上拉使能

    XP_AIN = 1<<4 * 1  选择nXPON 引脚输出值,设为1 表示nXPON 引脚输出1,则XP 引脚连接AIN[7] 引脚

    XM_HIZ = 1<<5 * 0  选择XMON 引脚输出值,设为0 表示XMON 引脚输出0,则XM 引脚为高阻态

    YP_AIN = 1<<6 * 1  选择nYPON 引脚输出值,设为1 表示nYPON 引脚输出1,则YP 引脚连接AIN[5] 引脚

    YM_GND = 1<<7 * 1  选择YMON 引脚输出值,设为1 表示YMON 引脚输出1,则YM 引脚为接地

    XP_PST(WAIT_INT_MODE); = 3  X坐标Y坐标手动测量设置,设为3 表示等待中断模式

    #ifdef CONFIG_DEVFS_FS

    devfs_ts_dir = devfs_mk_dir(NULL, "touchscreen", NULL);

    devfs_tsraw = devfs_register(devfs_ts_dir, "0raw", DEVFS_FL_DEFAULT,

    tsMajor, TSRAW_MINOR, S_IFCHR | S_IRUSR | S_IWUSR,

    &s3c2410_fops, NULL);

    #endif

    以上这两个函数在我总结的一篇《LCD驱动程序分析》中有较详细的介绍。

    这里调用了devfs_mk_dir 函数,在设备文件系统中创建了一个名为touchscreen 的目录,并返回一个带有目录结构的数据结构变量devfs_ts_dir。将该变量作为下一步devfs_register 函数的参数,该参数在调用设备文件系统注册清除函数devfs_unregister 时也要作为参数传入。

    调用devfs_register 函数后,会在刚才创建的touchscreen 目录下再创建一个名为0raw 的设备文件节点。该函数的参数中,DEVFS_FL_DEFAULT 为该函数的标志选项,tsMajor 为注册字符设备时系统自动分配的主设备号,TSRAW_MINOR(1)为次设备号,S_IFCHR | S_IRUSR | S_IWUSR 为默认的文件模式,&s3c2410_fops 为传入内核的触摸屏file_operations 结构中的函数接口,私有数据指针为空。返回一个devfs_handle_t 数据结构的变量devfs_tsraw,这会在调用设备文件系统注册清除函数devfs_unregister 时作为参数传入。

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

    模块的退出函数为s3c2410_ts_exit,该函数的工作就是清除已注册的字符设备,中断以及设备文件系统。

    #ifdef CONFIG_DEVFS_FS

    devfs_unregister(devfs_tsraw);

    devfs_unregister(devfs_ts_dir);

    #endif

    这里首先清除原先后一步创建设备文件节点0raw 的结构变量devfs_tsraw,然后再清除创建touchscreen 目录的结构变量devfs_ts_dir。

    unregister_chrdev(tsMajor, DEVICE_NAME);

    接下来删除字符设备的注册信息。

    在/kernel/arch/arm/kernel/irq.c 文件中:

    /**

    * free_irq - free an interrupt

    * @irq: Interrupt line to free

    * @dev_id: Device identity to free

    *

    * Remove an interrupt handler. The handler is removed and if the

    * interrupt line is no longer in use by any driver it is disabled.

    * On a shared IRQ the caller must ensure the interrupt is disabled

    * on the card it drives before calling this function.

    *

    * This function may be called from interrupt context.

    */

    void free_irq(unsigned int irq, void *dev_id)

    函数free_irq 与函数request_irq 相对应,通常在模块被卸载时调用,负责注销一个已经申请的中断。

    free_irq(IRQ_ADC_DONE, s3c2410_isr_adc);

    free_irq(IRQ_TC, s3c2410_isr_tc);

    最后依次注销A/D转换和定时器这两个已经申请的中断。

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

    接下来看一下A/D转换的中断处理函数:

    static void s3c2410_isr_adc(int irq, void *dev_id, struct pt_regs *reg)

    其中参数irq 为中断号,dev_id 为申请中断时告诉系统的设备标识,regs 为中断发生时寄存器内容。该函数在中断产生时由系统来调用,调用时以上参数已经由系统传入。

    在/kernel/include/linux/spinlock.h 文件中:

    /*

    * These are the generic versions of the spinlocks and read-write

    * locks..

    */

    #define spin_lock_irq(lock) do{local_irq_disable();spin_lock(lock);}while (0)

    #define spin_unlock_irq(lock) do{spin_unlock(lock);local_irq_enable();}while(0)

    #define DEBUG_SPINLOCKS 0 /* 0 == no debugging, 1 == maintain lock state, 2 == full debug */

    #if (DEBUG_SPINLOCKS < 1)

    typedef struct { } spinlock_t;

    #define SPIN_LOCK_UNLOCKED (spinlock_t) { }

    #define spin_lock_init(lock) do { } while(0)

    #define spin_lock(lock)  (void)(lock) /* Not "unused variable". */

    #define spin_unlock_wait(lock) do { } while(0)

    #define spin_unlock(lock) do { } while(0)

    可见上面这四个宏函数都是空函数,这样的话spin_lock_irq(lock)和spin_unlock_irq(lock)这两个宏函数 就相当于分别只调用了local_irq_disable();和local_irq_enable();两个宏函数。关于自旋锁的作用和概念可以参考一 篇《Linux内核的同步机制》文章的相关章节。

    在/kernel/include/asm-arm/system.h 文件中:

    #define local_irq_disable() __cli()

    #define local_irq_enable() __sti()

    在/kernel/include/asm-arm/proc-armo/system.h 文件中:

    /*

    * Enable IRQs

    */

    #define __sti()     "

    do {     "

    unsigned long temp;   "

    __asm__ __volatile__(   "

    " mov %0, pc  @ sti"n" "

    " bic %0, %0, #0x08000000"n"  "

    " teqp %0, #0"n"   "

    : "=r" (temp)    "

    :     "

    : "memory");    "

    } while(0)

    /*

    * Disable IRQs

    */

    #define __cli()     "

    do {     "

    unsigned long temp;   "

    __asm__ __volatile__(   "

    " mov %0, pc  @ cli"n" "

    " orr %0, %0, #0x08000000"n"  "

    " teqp %0, #0"n"   "

    : "=r" (temp)    "

    :     "

    : "memory");    "

    } while(0)

    最后用ARM 汇编指令实现了对IRQ 的使能和禁止。

    spin_lock_irq(&(tsdev.lock));

    这样调用spin_lock_irq 宏函数,实际上只是做了local_irq_disable();一步,就是禁止IRQ 中断。

    if (tsdev.penStatus == PEN_UP)

    s3c2410_get_XY();

    然后根据变量tsdev.penStatus 所处的状态,若为笔抬起则调用s3c2410_get_XY 函数来取得A/D转换得到的坐标值,该函数会在后面说明。

    #ifdef HOOK_FOR_DRAG

    else

    s3c2410_get_XY();

    #endif

    这里表示如果定义了笔拖曳,且在笔没有抬起的情况下,继续调用s3c2410_get_XY 函数来得到最新的坐标值。

    spin_unlock_irq(&(tsdev.lock));

    最后调用spin_unlock_irq 宏函数,相当于只做了local_irq_enable();一步,来重新使能IRQ 中断。最后退出这个中断服务子程序。

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

    继续来看一下另一个中断处理函数,即触摸屏触摸中断处理函数:

    static void s3c2410_isr_tc(int irq, void *dev_id, struct pt_regs *reg)

    该函数的参数和上面A/D转换中断处理函数的定义一样,不再累赘。

    spin_lock_irq(&(tsdev.lock));

    也同上面的意思一样,首先禁止IRQ 中断。

    if (tsdev.penStatus == PEN_UP) {

    start_ts_adc();

    }

    接着根据变量tsdev.penStatus 的状态值判断是否进行A/D转换。若笔抬起,则调用函数start_ts_adc 来进行A/D转换,该函数会在后面说明。

    else {

    tsdev.penStatus = PEN_UP;

    DPRINTK("PEN UP: x: %08d, y: %08d"n", x, y);

    wait_down_int();

    tsEvent();

    }

    如果变量tsdev.penStatus 的状态值不是笔抬起,则先将该变量状态设为笔抬起,然后调用宏函数wait_down_int()。该宏函数已在前面说明,用来设置触摸屏为等待中断模 式。最后调用tsEvent 函数指针所指的函数,在模块初始化函数s3c2410_ts_init 中,tsEvent 指向的是一个空函数tsEvent_dummy,而在打开设备函数s3c2410_ts_open 中,tsEvent 会指向tsEvent_raw 函数,该函数负责填充触摸屏缓冲区,并唤醒等待的进程。该函数也会在后面加以说明。

    spin_unlock_irq(&(tsdev.lock));

    中断处理函数的最后一步都一样,重新使能IRQ 中断。退出中断服务子程序。

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

    下面先来看启动A/D转换的函数:

    static inline void start_ts_adc(void)

    adc_state = 0;

    mode_x_axis();

    start_adc_x();

    简简单单的3步。

    第一步,对A/D转换的状态变量清零。

    第二步,调用mode_x_axis 宏函数,具体定义如下:

    #define mode_x_axis() { ADCTSC = XP_EXTVLT | XM_GND | YP_AIN | YM_HIZ | "

    XP_PULL_UP_DIS | XP_PST(X_AXIS_MODE); }

    //*******************************************************

    //* 2007.6.27

    //*******************************************************

    该宏函数用来设置ADC触摸屏控制寄存器为测量X坐标模式,参考S3C2410 芯片datasheet 中关于触摸屏的章节,具体设置参数如下:

    XP_EXTVLT = 1<<4 * 0  选择nXPON 引脚输出值,设为0 表示nXPON 引脚输出0,则XP 引脚为接外部电压

    XM_GND = 1<<5 * 1  选择XMON 引脚输出值,设为1 表示XMON 引脚输出1,则XM 引脚为接地

    YP_AIN = 1<<6 * 1  选择nYPON 引脚输出值,设为1 表示nYPON 引脚输出1,则YP 引脚连接AIN[5] 引脚

    YM_HIZ = 1<<7 * 0  选择YMON 引脚输出值,设为0 表示YMON 引脚输出0,则YM 引脚为高阻态

    XP_PULL_UP_DIS = 1<<3 * 1  上拉开关使能,设为1 表示XP 引脚上拉禁止

    XP_PST(X_AXIS_MODE); = 1  X坐标Y坐标手动测量设置,设为1 表示X坐标测量模式

    第三步,调用start_adc_x 宏函数,具体定义如下:

    #define start_adc_x() { ADCCON = PRESCALE_EN | PRSCVL(49) | "

    ADC_INPUT(ADC_IN5) | ADC_START_BY_RD_EN | "

    ADC_NORMAL_MODE; "

    ADCDAT0; }

    该宏函数用来设置ADC控制寄存器启动X坐标的A/D转换,参考S3C2410 芯片datasheet 中关于触摸屏的章节,具体设置参数如下:

    PRESCALE_EN = 1<<14 * 1  A/D转换器使能,设为1 表示使能A/D转换器

    PRSCVL(49) = 49<<6  A/D转换器值,设为49

    ADC_INPUT(ADC_IN5) = 5<<3  选择模拟输入通道,设为5 表示AIN[5] 引脚作为模拟输入通道

    ADC_START_BY_RD_EN = 1<<1 * 1  A/D转换通过读启动,设为1 表示通过读操作启动A/D转换使能

    ADC_NORMAL_MODE; = 1<<2 * 0  选择待命模式,设为0 表示正常操作模式

    ADCDAT0;  读取X坐标的ADC转换数据寄存器

    由于设置了A/D转换通过读启动,则该ADCCON 寄存器的最低位ENABLE_START 启动A/D转换位就无效了。在最后一步读取ADCDAT0 寄存器这一操作时就启动了A/D转换。

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

    static inline void s3c2410_get_XY(void)

    这就是获取A/D转换所得到的坐标值的函数。

    if (adc_state == 0)

    {

    adc_state = 1;

    disable_ts_adc();

    y = (ADCDAT0 & 0x3ff);

    mode_y_axis();

    start_adc_y();

    }

    这里首先查看A/D转换的状态变量,若为0 表示进行过X坐标的A/D转换,将该变量设为1。然后调用宏函数disable_ts_adc,该宏函数定义如下:

    #define disable_ts_adc() { ADCCON &= ~(ADCCON_READ_START); }

    这个宏函数主要工作就是禁止通过读操作启动A/D转换,参考S3C2410 芯片datasheet 中关于触摸屏的章节,具体设置参数如下:

    ADCCON_READ_START = 1<<1  A/D转换通过读启动,设为0 表示通过读操作启动A/D转换禁止

    然后y = (ADCDAT0 & 0x3ff); 这一步将X坐标的ADC转换数据寄存器的D9~D0 这10为读出到变量y(这里由于是竖屏,参考原理图后知道,硬件连线有过改动,将XP,XM 和YP,YM 进行了对换,这样ADCDAT0 里读出的是YP,YM 方向电阻导通的值,也就是y轴坐标值)。这个mode_y_axis 宏函数定义如下:

    #define mode_y_axis() { ADCTSC = XP_AIN | XM_HIZ | YP_EXTVLT | YM_GND | "

    XP_PULL_UP_DIS | XP_PST(Y_AXIS_MODE); }

    该宏函数用来设置ADC触摸屏控制寄存器为测量Y坐标模式,参考S3C2410 芯片datasheet 中关于触摸屏的章节,具体设置参数如下:

    XP_AIN = 1<<4 * 1  选择nXPON 引脚输出值,设为1 表示nXPON 引脚输出1,则XP 引脚连接AIN[7] 引脚

    XM_HIZ = 1<<5 * 0  选择XMON 引脚输出值,设为0 表示XMON 引脚输出0,则XM 引脚为高阻态

    YP_EXTVLT = 1<<6 * 0  选择nYPON 引脚输出值,设为0 表示nYPON 引脚输出0,则YP 引脚为接外部电压

    YM_GND = 1<<7 * 1  选择YMON 引脚输出值,设为1 表示YMON 引脚输出1,则YM 引脚为接地

    XP_PULL_UP_DIS = 1<<3 * 1  上拉开关使能,设为1 表示XP 引脚上拉禁止

    XP_PST(Y_AXIS_MODE); = 2  X坐标Y坐标手动测量设置,设为2 表示Y坐标测量模式

    最后调用start_adc_y 宏函数,具体定义如下:

    #define start_adc_y() { ADCCON = PRESCALE_EN | PRSCVL(49) | "

    ADC_INPUT(ADC_IN7) | ADC_START_BY_RD_EN | "

    ADC_NORMAL_MODE; "

    ADCDAT1; }

    该宏函数用来设置ADC控制寄存器启动Y坐标的A/D转换,参考S3C2410 芯片datasheet 中关于触摸屏的章节,具体设置参数如下:

    PRESCALE_EN = 1<<14 * 1  A/D转换器使能,设为1 表示使能A/D转换器

    PRSCVL(49) = 49<<6  A/D转换器值,设为49

    ADC_INPUT(ADC_IN7) = 7<<3  选择模拟输入通道,设为7 表示AIN[7] 引脚作为模拟输入通道

    ADC_START_BY_RD_EN = 1<<1 * 1  A/D转换通过读启动,设为1 表示通过读操作启动A/D转换使能

    ADC_NORMAL_MODE; = 1<<2 * 0  选择待命模式,设为0 表示正常操作模式

    ADCDAT1;  读取Y坐标的ADC转换数据寄存器

    else if (adc_state == 1)

    {

    adc_state = 0;

    disable_ts_adc();

    x = (ADCDAT1 & 0x3ff);

    tsdev.penStatus = PEN_DOWN;

    DPRINTK("PEN DOWN: x: %08d, y: %08d"n", x, y);

    wait_up_int();

    tsEvent();

    }

    若查看A/D转换的状态变量,若为1 表示进行过Y坐标的A/D转换,将该变量设为0。然后调用宏函数disable_ts_adc 来禁止通过读操作启动A/D转换。

    接着将x = (ADCDAT1 & 0x3ff); 这一步将Y坐标的ADC转换数据寄存器的D9~D0 这10为读出到变量x(这里由于是竖屏,参考原理图后知道,硬件连线有过改动,将XP,XM 和YP,YM 进行了对换,这样ADCDAT1 里读出的是XP,XM 方向电阻导通的值,也就是x轴坐标值)。

    随后将变量tsdev.penStatus 的状态值改为笔按下,并调用wait_up_int 宏函数来设置触摸屏为等待中断模式【笔抬起产生中断】,具体定义如下:

    #define wait_up_int() { ADCTSC = UP_INT | XP_PULL_UP_EN | XP_AIN | XM_HIZ | "

    YP_AIN | YM_GND | XP_PST(WAIT_INT_MODE); }

    用该宏函数来设置ADC 触摸屏控制寄存器,参考S3C2410 芯片datasheet 中关于触摸屏的章节,具体设置参数如下:

    UP_INT = 1<<8 * 1  该位保留且应该设为0,这里设为1 不知道为什么 【笔按下或笔抬起中断信号控制位,设为1 表示笔抬起产生中断信号】

    XP_PULL_UP_EN = 1<<3 * 0  上拉开关使能,设为0 表示XP 引脚上拉使能

    XP_AIN = 1<<4 * 1  选择nXPON 引脚输出值,设为1 表示nXPON 引脚输出1,则XP 引脚连接AIN[7] 引脚

    XM_HIZ = 1<<5 * 0  选择XMON 引脚输出值,设为0 表示XMON 引脚输出0,则XM 引脚为高阻态

    YP_AIN = 1<<6 * 1  选择nYPON 引脚输出值,设为1 表示nYPON 引脚输出1,则YP 引脚连接AIN[5] 引脚

    YM_GND = 1<<7 * 1  选择YMON 引脚输出值,设为1 表示YMON 引脚输出1,则YM 引脚为接地

    XP_PST(WAIT_INT_MODE); = 3  X坐标Y坐标手动测量设置,设为3 表示等待中断模式

    最后调用函数指针tsEvent 所指向的函数。在s3c2410_get_XY 函数里面,应该表示这个驱动的设备文件已经打开,在打开设备文件函数中,tsEvent 函数指针就指向了tsEvent_raw 这个函数,也就是说,下面执行的是tsEvent_raw 函数。tsEvent_raw 函数负责填充触摸屏缓冲区,并唤醒等待的进程,该函数会在后面说明。

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

    static void tsEvent_raw(void)

    来看一下tsEvent_raw 这个函数。

    if (tsdev.penStatus == PEN_DOWN)

    {

    BUF_HEAD.x = x;

    BUF_HEAD.y = y;

    BUF_HEAD.pressure = PEN_DOWN;

    一上来就根据变量tsdev.penStatus 的状态值进行判断,若为笔按下,将从A/D转换器中采集的x轴y轴坐标以及笔按下的状态存入变量tsdev 中的buf 成员中的相应变量中。

    #ifdef HOOK_FOR_DRAG

    ts_timer.expires = jiffies + TS_TIMER_DELAY;

    add_timer(&ts_timer);

    #endif

    如果定义了笔拖曳,先将定时器的定时溢出值更新,然后调用add_timer 函数重新增加定时器计时,变量ts_timer 为struct timer_list 数据结构。

    }

    else

    {

    #ifdef HOOK_FOR_DRAG

    del_timer(&ts_timer);

    #endif

    如果定义了笔拖曳,调用del_timer 函数来删除定时器,变量ts_timer 为struct timer_list 数据结构。

    BUF_HEAD.x = 0;

    BUF_HEAD.y = 0;

    BUF_HEAD.pressure = PEN_UP;

    }

    若变量tsdev.penStatus 的状态值不是笔按下,则x轴y轴坐标写为0和笔抬起的状态一起存入变量tsdev 中的buf 成员中的相应变量中。

    tsdev.head = INCBUF(tsdev.head, MAX_TS_BUF);

    其中INCBUF 宏函数定义如下:

    #define INCBUF(x,mod)  ((++(x)) & ((mod) - 1))

    由于这里MAX_TS_BUF=16,这样(mod) - 1)就为15(0x0F),所以这个宏函数相当于就是将变量tsdev.head 这个表示buf 头位置的值加1,然后取模16,即指向下一个buf ,形成一个在环形缓冲区上绕环的头指针。

    在/kernel/include/linux/sched.h 文件中:

    #define wake_up_interruptible(x) __wake_up((x),TASK_INTERRUPTIBLE, 1)

    该宏函数定义为__wake_up 函数,参数TASK_INTERRUPTIBLE 为1,表示要唤醒的任务的状态为中断模式,参数1 表示要唤醒的互斥进程数目为1。

    对 应的唤醒操作包括wake_up_interruptible和wake_up。wake_up函数不仅可以唤醒状态为 TASK_UNINTERRUPTIBLE的进程,而且可以唤醒状态为TASK_INTERRUPTIBLE的进程。 wake_up_interruptible只负责唤醒状态为TASK_INTERRUPTIBLE的进程。关于 interruptible_sleep_on 和wake_up_interruptible 函数详细的用法可以参考一篇《关于linux内核中等待队列的问题》文档。

    在/kernel/kernel/sched.c 文件中:

    void __wake_up(wait_queue_head_t *q, unsigned int mode, int nr)

    {

    if (q) {

    unsigned long flags;

    wq_read_lock_irqsave(&q->lock, flags);

    __wake_up_common(q, mode, nr, 0);

    wq_read_unlock_irqrestore(&q->lock, flags);

    }

    }

    宏函数wq_read_lock_irqsave 的作用主要就是保存IRQ 和FIQ 的中断使能状态,并禁止IRQ 中断;而宏函数wq_read_unlock_irqrestore 的作用就是恢复IRQ 和FIQ 的中断使能状态。现在可以得知__wake_up 这个函数的作用,它首先保存IRQ 和FIQ 的中断使能状态,并禁止IRQ 中断,接着调用__wake_up_common 函数来唤醒等待q 队列的进程,最后再恢复IRQ 和FIQ 的中断使能状态。

    /*

    * The core wakeup function.  Non-exclusive wakeups (nr_exclusive == 0) just wake everything

    * up.  If it's an exclusive wakeup (nr_exclusive == small +ve number) then we wake all the

    * non-exclusive tasks and one exclusive task.

    *

    * There are circumstances in which we can try to wake a task which has already

    * started to run but is not in state TASK_RUNNING.  try_to_wake_up() returns zero

    * in this (rare) case, and we handle it by contonuing to scan the queue.

    */

    static inline void __wake_up_common (wait_queue_head_t *q, unsigned int mode,

    int nr_exclusive, const int sync)

    该函数的作用是唤醒在等待当前等待队列的进程。参数q 表示要操作的等待队列,mode 表示要唤醒任务的状态,如TASK_UNINTERRUPTIBLE 或TASK_INTERRUPTIBLE 等。nr_exclusive 是要唤醒的互斥进程数目,在这之前遇到的非互斥进程将被无条件唤醒。sync表示???

    在/kernel/include/linux/wait.h 文件中:

    struct __wait_queue_head {

    wq_lock_t lock;

    struct list_head task_list;

    #if WAITQUEUE_DEBUG

    long __magic;

    long __creator;

    #endif

    };

    typedef struct __wait_queue_head wait_queue_head_t;

    这是等待队列数据结构。

    # define wq_read_lock_irqsave spin_lock_irqsave

    # define wq_read_unlock_irqrestore spin_unlock_irqrestore

    看到这里可以知道其实宏函数wq_read_lock_irqsave 和wq_read_unlock_irqrestore 等价于宏函数spin_lock_irqsave 和spin_unlock_irqrestore,并直接将自己的参数传了下去。

    在/kernel/include/linux/spinlock.h 文件中:

    /*

    * These are the generic versions of the spinlocks and read-write

    * locks..

    */

    #define spin_lock_irqsave(lock, flags)  do {local_irq_save(flags);spin_lock(lock);}while (0)

    #define spin_unlock_irqrestore(lock, flags)  do {spin_unlock(lock);  local_irq_restore(flags); } while (0)

    在这两个宏函数中,前面已经提到spin_lock 和spin_unlock 其实都为空函数,那么实际只执行了local_irq_save 和local_irq_restore 这两个宏函数。

    在/kernel/include/asm-arm/system.h 文件中:

    /* For spinlocks etc */

    #define local_irq_save(x) __save_flags_cli(x)

    #define local_irq_restore(x) __restore_flags(x)

    这里local_irq_save 和local_irq_restore 这两个宏函数又分别等价于__save_flags_cli 和__restore_flags 这两个宏函数。

    在/kernel/include/asm-arm/proc-armo/system.h 文件中:

    /*

    * A couple of speedups for the ARM

    */

    /*

    * Save the current interrupt enable state & disable IRQs

    */

    #define __save_flags_cli(x)    "

    do {      "

    unsigned long temp;    "

    __asm__ __volatile__(    "

    " mov %0, pc  @ save_flags_cli"n" "

    " orr %1, %0, #0x08000000"n"   "

    " and %0, %0, #0x0c000000"n"   "

    " teqp %1, #0"n"    "

    : "=r" (x), "=r" (temp)   "

    :      "

    : "memory");     "

    } while (0)

    /*

    * restore saved IRQ & FIQ state

    */

    #define __restore_flags(x)    "

    do {      "

    unsigned long temp;    "

    __asm__ __volatile__(    "

    " mov %0, pc  @ restore_flags"n" "

    " bic %0, %0, #0x0c000000"n"   "

    " orr %0, %0, %1"n"    "

    " teqp %0, #0"n"    "

    : "=&r" (temp)    "

    : "r" (x)     "

    : "memory");     "

    } while (0)

    最后用ARM 汇编指令实现了对当前程序状态寄存器CPSR 中的IRQ 和FIQ 中断使能状态的保存和恢复。而且在__save_flags_cli 宏函数中,除了对IRQ 和FIQ 中断使能状态的保存外,还禁止了IRQ 中断。

    wake_up_interruptible(&(tsdev.wq));

    在这个tsEvent_raw 函数最后,调用wake_up_interruptible 函数来以中断模式唤醒等待tsdev.wq 队列的进程。

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

    由于上面这个wake_up_interruptible 函数的作用还不是很明确,所以需要分析一下打开设备文件这个函数。触摸屏打开设备文件函数定义如下:

    static int s3c2410_ts_open(struct inode *inode, struct file *filp)

    tsdev.head = tsdev.tail = 0;

    tsdev.penStatus = PEN_UP;

    首先是最简单的将变量tsdev.head 和tsdev.tail 这两个表示buf 头尾位置的值清零,然后将变量tsdev.penStatus 的状态值初始化为笔抬起。

    #ifdef HOOK_FOR_DRAG

    init_timer(&ts_timer);

    ts_timer.function = ts_timer_handler;

    #endif

    如果定义了笔拖曳,先调用init_timer 函数来初始化一个定时器,变量ts_timer 为struct timer_list 数据结构,然后将定时调用的函数指针指向ts_timer_handler 函数。

    tsEvent = tsEvent_raw;

    将函数指针tsEvent 指向tsEvent_raw 函数。

    在/kernel/include/linux/wait.h 文件中:

    static inline void init_waitqueue_head(wait_queue_head_t *q)

    {

    #if WAITQUEUE_DEBUG

    if (!q)

    WQ_BUG();

    #endif

    q->lock = WAITQUEUE_RW_LOCK_UNLOCKED;

    INIT_LIST_HEAD(&q->task_list);

    #if WAITQUEUE_DEBUG

    q->__magic = (long)&q->__magic;

    q->__creator = (long)current_text_addr();

    #endif

    }

    该函数初始化一个已经存在的等待队列头,它将整个队列设置为"未上锁"状态,并将链表指针prev和next指向它自身。

    init_waitqueue_head(&(tsdev.wq));

    在这个s3c2410_ts_open 函数中,调用init_waitqueue_head 函数来初始化一个定义在变量tsdev 中的等待队列头的成员结构。

    MOD_INC_USE_COUNT;

    return 0;

    最后调用MOD_INC_USE_COUNT; 来对设备文件计数器加一计数,并返回。

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

    再来分析一下用户层要调用的读取设备文件的接口函数:

    static ssize_t s3c2410_ts_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)

    这个函数实现的任务是将事件队列从设备缓存中读到用户空间的数据缓存中。实现的过程主要是通过一个循环,只有在事件队列的头、尾指针不重合时,才能成功的 从tsdev.tail指向的队列尾部读取到一组触摸信息数据,并退出循环。否则调用读取函数的进程就要进入睡眠。

    TS_RET ts_ret;

    retry:

    if (tsdev.head != tsdev.tail)

    {

    int count;

    count = tsRead(&ts_ret);

    if (count) copy_to_user(buffer, (char *)&ts_ret, count);

    return count;

    }

    这个函数中,首先通过变量tsdev.head 和tsdev.tail 是否相等来判断环形缓冲区是否为空。若不相等则表示环形缓冲区中有触摸屏数据,调用tsRead 函数将触摸屏数据读入TS_RET 数据结构的ts_ret 变量中,该函数会在后面说明。

    接下来调用copy_to_user 函数来把内核空间的数据复制到用户空间,在这里就是把驱动程序里的变量ts_ret 中的数据复制到用户程序的buffer 中,然后返回复制的数据长度。

    else

    {

    if (filp->f_flags & O_NONBLOCK)

    return -EAGAIN;

    interruptible_sleep_on(&(tsdev.wq));

    if (signal_pending(current))

    return -ERESTARTSYS;

    goto retry;

    }

    若变量tsdev.head 和tsdev.tail 相等,则表示环形缓冲区为空,首先根据file->f_flags 与上O_NONBLOCK 值来进行判断,若为O_NONBLOCK 值,则表示采用非阻塞的文件IO方法,立即返回,否则才会调用interruptible_sleep_on 函数,调用该函数的进程将会进入睡眠,直到被唤醒。关于O_NONBLOCK 值的含义在我总结的《IIS音频驱动程序分析》一文中有详细说明。

    在/kernel/kernel/sched.c 文件中:

    void interruptible_sleep_on(wait_queue_head_t *q)

    {

    SLEEP_ON_VAR

    current->state = TASK_INTERRUPTIBLE;

    SLEEP_ON_HEAD

    schedule();

    SLEEP_ON_TAIL

    }

    常用的睡眠操作有interruptible_sleep_on和sleep_on。两个函数类似,只不过前者将进程的状态从就绪态 (TASK_RUNNING)设置为TASK_INTERRUPTIBLE,允许通过发送signal唤醒它(即可中断的睡眠状态);而后者将进程的状态 设置为TASK_UNINTERRUPTIBLE,在这种状态下,不接收任何singal。关于interruptible_sleep_on 和wake_up_interruptible 函数详细的用法可以参考一篇《关于linux内核中等待队列的问题》文档。

    如果进程被唤醒,则会继续跳到retry: 循环读取环形缓冲区,直到读取到一组触摸信息数据才会退出。

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

    static int tsRead(TS_RET * ts_ret)

    这个函数主要将环形缓冲区的x轴y轴坐标数据和笔的状态数据传入该函数形式参数所指的指针变量中。

    spin_lock_irq(&(tsdev.lock));

    上文已经解释过了,调用该宏函数来禁止IRQ 中断。

    ts_ret->x = BUF_TAIL.x;

    ts_ret->y = BUF_TAIL.y;

    ts_ret->pressure = BUF_TAIL.pressure;

    tsdev.tail = INCBUF(tsdev.tail, MAX_TS_BUF);

    接着把变量tsdev 的环形缓冲区中相关数据赋值给该函数形式参数所指的指针变量中,并将表示环形缓冲区队列尾部的变量tsdev.tail 加一,这就意味着从环形队列中读取一组数据,尾指针加一。

    spin_unlock_irq(&(tsdev.lock));

    return sizeof(TS_RET);

    上文已经解释过了,调用该宏函数来重新使能IRQ 中断,然后返回。

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

    再来看一个定时器定时调用的函数:

    #ifdef HOOK_FOR_DRAG

    static void ts_timer_handler(unsigned long data)

    {

    spin_lock_irq(&(tsdev.lock));

    if (tsdev.penStatus == PEN_DOWN) {

    start_ts_adc();

    }

    spin_unlock_irq(&(tsdev.lock));

    }

    #endif

    这个函数需要定义过笔拖曳才有效。首先调用宏函数spin_lock_irq 来禁止IRQ 中断。

    然后在变量tsdev.penStatus 状态为笔按下的时候,调用宏函数start_ts_adc 来启动A/D转换,转换的是X轴的坐标。

    最后再调用宏函数spin_unlock_irq 来重新使能IRQ 中断。

    //*******************************************************

    //* 2007.6.28

    //*******************************************************

    最后再来看一个释放设备文件的函数:

    static int s3c2410_ts_release(struct inode *inode, struct file *filp)

    #ifdef HOOK_FOR_DRAG

    del_timer(&ts_timer);

    #endif

    MOD_DEC_USE_COUNT;

    return 0;

    其实也很简单,在定义了笔拖曳的情况下,调用del_timer 函数来删除定时器,变量ts_timer 为struct timer_list 数据结构,然后调用MOD_DEC_USE_COUNT; 将设备文件计数器减一计数,并返回。

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

    经过对整个触摸屏驱动程序的流程,以及S3C2410 芯片数据手册里的相关章节进行分析后,下面来总结一下触摸屏驱动程序的大致流程。

    首先在驱动模块初始化函数中,除了对驱动的字符设备的注册外,还要对中断进行申请。这里申请了两个触摸屏相关的中断,一个是IRQ_TC 中断,查阅了数据手册后了解到,该中断在笔按下时,由XP 管脚产生表示中断的低电平信号,而笔抬起是没有中断信号产生的。另一个是IRQ_ADC_DONE 中断,该中断是当芯片内部A/D转换结束后,通知中断控制器产生中断,这时就可以去读取转换得到的数据。

    当触摸屏按下后,就会出发中断,这时会调用申请中断时附带的s3c2410_isr_tc 中断回调函数,该函数中判断若为笔抬起则启动x轴坐标的A/D转换。当转换完毕后就会产生ADC中断,这时就会调用申请中断时附带的 s3c2410_isr_adc 中断回调函数,在该函数中进行判断,若x轴坐标转换结束马上进行y轴坐标的A/D转换转换;若y轴坐标转换结束,则重新回到等待中断模式,然后将坐标值写 入环形缓冲区,并环形等待队列中的进程。

    //*******************************************************

    //* 2007.6.29

    //*******************************************************

    昨天晚上经过触摸屏驱动的调试,看出在S3C2410 的datasheet 中有一个问题。关于触摸屏中的ADC 触摸屏控制寄存器ADCTSC 的第8位,在原来的datasheet 中说该为保留,应设为0。而实际调试下来,该位是有功能的,参考了S3C2440 的datasheet 后得知ADCTSC 寄存器的第8位是笔按下或抬起的中断信号控制位,该位设为0,笔按下产生中断信号,该位设为1,笔抬起产生中断信号。经过测试,确实在笔按下和抬起时都会 产生中断,并两次调用了s3c2410_isr_tc 中断回调函数。

    现在要对前面涉及到ADCTSC 寄存器配置部分的说明做些改动,用“【 】”加以区分。

    展开全文
  • 创建虚拟的U-boot和ARM Linux学习环境

    千次阅读 2012-01-04 15:44:32
    创建虚拟的U-boot和ARM Linux学习环境 作者:YF-YF 日期:2009-12-9 关键词:U-boot,VersatilePB, ARM, Linux, Qemu, PB926,RealView Platforms 本文介绍了一种不需要花钱购买真实的电路板,就可以学习基于...
  • 原文链接:http://bbs.eetop.cn/thread-224249-1-1.html创建虚拟的U-boot和ARM Linux学习环境 作者:YF-YF日期:2009-12-9关键词:U-boot,VersatilePB, ARM, Linux, Qemu, PB926,RealView Platforms 本文介绍了一种...
  • arm linux学习之路 1

    2015-04-12 11:56:39
    本人是菜鸟,以前接触过部分单片机,正好有arm的开发板,正好借此机会学习一下,以后每天都将会把遇到的问题,还有能解决的方法放到这里与大家讨论。 今天遇到的问题是nfs的挂载,我遇到的问题和解决的办法如下,...
  • 最近打算开始学习ARM+Linux方面的东西,一来因为本身是学嵌入式专业的,而ARM在嵌入式方向的应用又十分广,但到目前为止平时用到的都是基于Ateml AVR的一些板子,如Arduino、Atmel Raven,接触到的嵌入式系统也只有...
  • 引子 开始学ARM 1. 用64位win7 的VMware11安装Ubuntu14.10 64位,用tar zxvf arm-linux-gcc4.4.3.tar.gz 用arm-linux-gcc -v 检测是否成功但是都不行 原因是arm-linux-gcc是32位的,在64位的Ubuntu不兼容
  • armlinux学习笔记--IIS音频驱动程序分析  //******************************************************* //* 2007.7.9 //*******************************************************  ...
  • armlinux学习笔记--触摸屏驱动程序分析 //******************************************************* //*2007.6.26 //******************************************************* Linux 下的触摸屏驱动程序...
  • 在PC上,用gcc编译生成的程序,在ARM上运行会出错,在PC机上要使用交叉编译链arm-linux-gcc对程序进行编译。交叉编译链运行在宿主机上,这里使用的是ubuntu14.04的笔记本电脑。 在进行交叉编译之前,首先需要安装
  • armlinux学习笔记--触摸屏驱动程序分析//*******************************************************//* 2007.6.26//******************************************************* Linux 下的触摸屏驱动程序主要都在...
  • ...U-boot,VersatilePB, ARM, Linux, Qemu, PB926, RealView Platfor ms <br />本文介绍了一种不需要花钱购买真实的电路 板,就可以学习基于 ARM的嵌入式 l

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,657
精华内容 1,462
关键字:

armlinux学习

linux 订阅