随笔-118  评论-133  文章-4  trackbacks-0
 
为什么IT人一定要离开IT呢?

今天一位同事(已三十多了),被老板叫到他的办公室去。老总对他说,由于效益不好,公司不再和他续订到期的合同!我看到他很落寞地离去。他前年才结婚,还 供着房子,老婆孩子要养,这一下子就失业了,而且又是快过年的时候!我跑去和他道别,他没有说什么,只让我好好干,公司还是大有前途的。

他是一个好人,在技术上决不保密。记得三年前我刚进公司的时候,他正是公司的主力,他对我这个应届毕业生十分关照,只要我不懂的,他一定尽力相告。那时公 司的老板也很器重他,可能是正当壮年的时候(还有很多剥削价值)。但自去年开始,公司转向了。NET平台,我们都去研发新技术了,原有的PB老版本程序基 本上都由一些老程序员来维护(可能老板想他们年龄不小了,学新技术有些障碍)。公司产品的升级工作进展很快,PB版本的程序越来越少了,我们晚上经常加 班,而他由于年龄和家庭的缘故,并不经常加班了。我渐渐地从老板对他的态度的变化——从最早的极为欣赏到一般到渐渐地嫌弃。今年公司的效益不太好,也许正 是到了鸟尽弓藏、兔死狗烹的时候了? 上世纪末那会儿,曾有"做IT,35岁就可以退休"的说法,历经沧海这么多年,35岁退休成了童话,35岁的职业坎儿却无法让众多IT人回避。有人说,可 以转为管理,然而管理的一条线就像窄窄的独木桥,又有多少人可以通过呢(据统计平均大约一百个程序员也就只有一两个做管理的机会)?转向传统行业?已经 30多了,能再重新来过?绝大多数平凡IT人的出路又在哪儿呢?

35岁对于IT人是个坎儿,过了这个年纪基本技术生涯即告终结,这是IT界多数人认可的。所以,也让IT人着实为35岁后的自己生了不少忧虑:IT人离开IT后能干什么?

印象中的IT人都因为职业的缘故而木讷寡言。他们与机器沟通的能力显然优于与人沟通的能力。从机器到人,IT人必须跨过来。 IT业的技术语言过于狭窄。社会却是复杂的。IT人的知识面不够广泛。社会上不需要人人都懂如何设计程序,但IT人却必须要懂社会。

IT内的项目,有些人也有些经验,但这些经验性的东西多数是专业性的,个别种类项目的经验能否转化为普遍的社会经验,也确实需要时间。

这些劣势,可以说,凡是地球上的IT人大概都知道。除了IT,要重新换个活法一时还真的玩不转。 但IT人毕竟是IT人。IT是高薪行业,IT人从业几年十几年,一般都有了一定的物质基础。这是IT人比其他行业的人具有的优势。

有了这样的物质基础,我认为,后IT人的关键是要实现思维方式的转变:从技术性思维到社会性思维,从而开创人生事业的第二高峰。
技术性思维是面向机器的、僵硬的、封闭的、单向性的;社会性思维要求是面向常识和社会的、灵活的、开放的、多向综合的。后IT的人士最需要的是这种思维方式的转变。

思维一转天地宽。后IT人会发现社会比IT里面其实更精彩。 后IT人可以走出来干销售。这个工作富于挑战,而且收入与工作业绩直接挂钩。IT人有很强的技术背景,更擅长发掘产品性能的优缺点,对于IT产品的介绍具 有权威性,容易被客户相信。 后IT人可以走出程序迷宫来做培训。IT人在运用某一技术语言上相当精深,另外他们在技术研发中的实战经验,对于学员来说也是相当宝贵的教学资源。 后IT人也可以由直面数字转为面向众人做咨询。成功的咨询师决不会因为年龄而贬值。有过实际项目经验的IT人,解决实际问题的能力非常强,出身IT的人作 为咨询师是其他行业人士无法取代的。还可以做老板,做IT活动策划等等。

需要提醒的是,思维方式的转变不是一朝一夕的事。一旦离开IT,IT人所要做的是不断调整自我,保持对社会的参与激情。阅读一些人际沟通技巧之类的书籍是 必要的,还可以参加社会活动,给自己洗洗脑,从以数字中心、个人中心的思维方式转换到以人为中心、社会为中心的模式中。要注意多与人沟通。同时,个人应该 尽早明确自己的发展方向,并根据新的事业来重新积累,不断升级完善自身的"软硬件"。
posted @ 2008-04-23 17:24 lfc 阅读(818) | 评论 (0)编辑 收藏

    BIOS下红外接口默认关闭,而linux-2.6.17前没有pnp处理,需要进入BIOS下手动开始或对内核打补丁,详细请参考:
http://www.thinkwiki.org/wiki/How_to_make_use_of_IrDA


    在我使用的FC4上已经安装了irda-util,版本为0.9.16-7,然而系统自带的irda启动脚本不能正常启动irda接口,参考网上资料,采用以下启动脚本:
http://www.51nb.com/forum/thread-246611-1-1.html

#!/bin/sh
#Start up the IrDA process and load necessary modules
#
case "$1" in
  start)
    #Start IRDA
    echo -n "Starting up the IR modules"

    modprobe irda
    modprobe ircomm
    modprobe ircomm-tty
    irattach /dev/ttyS1 -s
   
    echo "Done."
    echo ""
    ;;

  stop)
    #KILL IRDA
    echo -n "Stopping IRDA and removing used modules"

    #Kill the irattach process and remove the modules
    killall -9 irattach
    rmmod ircomm-tty ircomm irda

    echo "Done."
    echo ""
    ;;

  *)
    echo "Usage:irdastart.sh{start|stop}"
    echo ""
    exit 1
   
    esac
    exit 0
保存
chmod 755 myirda
chmod 755 /dev/ircomm0
开启:
./myirda start
关闭:
./myirda stop

测试:
打开手机红外线,对准接收窗口,./myirda start,运行irdadump命令,应该有数据显示,否则红外设置不成功

正常情况下可以看到类似以下信息:
16:59:12.083873 xid:cmd 9044ea60 > ffffffff S=6 s=* localhost hint=4400 [ Computer LAN Access ] (25)
16:59:12.082870 xid:rsp 9044ea60 < 0000277b S=6 s=5 Nokia 6020 hint=b125 [ PnP Modem Fax Telephony IrCOMM IrOBEX ] (27)


    下载openobex

./configure --enable-apps
make
make install


使用:
Sending from computer:
irobex_palm3 /path/to/file.xxx
 
Recieving to computer:
irobex_palm3



注:
    使用ubuntu的朋友可以参考这个贴子:
http://ubuntuforums.org/showpost.php?p=504374&postcount=2
posted @ 2008-04-09 01:05 lfc 阅读(560) | 评论 (0)编辑 收藏
环境:
    linux-2.6.22
    busybox-1.4.2
    udev-115
    arm-linux-gcc-3.4.5
 
1、telnet服务器搭建:

   使用busybox自带的telnetd

    注意事项:
        需要内核支持devpts文件系统,并挂载到/dev/pts目录下(没有这个目录的话,可以在启动脚本里自行创建)

    启动:
       执行/usr/sbin/telnetd&即可

    访问:
       在pc下执行telnet xxx.xxx.xxx.xxx,按照提示输入用户、密码即可。


2、ftp服务器搭建:

   这里有一个非常详细的文档介绍,可以参考一下:

   http://www.vsftpdrocks.org/source/



posted @ 2008-03-07 14:43 lfc 阅读(435) | 评论 (0)编辑 收藏
近日研究vfs文件系统,继而研究hlist链表,曾经一度给迷惑,好不容易弄明白。本想写个帖子,可是发现已经有很好的贴可供参考了。自问写不出这么好的帖子,还是转一下好了^_^
注:
    原文出处:http://blog.chinaunix.net/u/12592/showart.php?id=451619

王耀(wangyao@cs.hit.edu.cn)

hlist哈希链表是内核中常用的一个数据结构,由于它不同于普通的链表,所以这里对hlist哈希链表进行一下分析,希望对大家有所帮助。
在include/Linux/list.h中有list链表与hlist哈希链表结构的定义,下面都列出它们的定义,可以对比一下:
struct list_head {
struct list_head *next, *prev;
};

struct hlist_head {
struct hlist_node *first;
};

struct hlist_node {
struct hlist_node *next, **pprev;
};

双头(next,prev)的双链表对于Hash表来说“过于浪费”,因而另行设计了一套Hash表专用的hlist数据结构——单指针表头双循环 链表,hlist的表头仅有一个指向首节点的指针,而没有指向尾节点的指针,这样在可能是海量的Hash表中存储的表头就能减少一半的空间消耗。
pprev因为hlist不是一个完整的循环链表而不得不使用。在list中,表头和节点是同一个数据结构,直接用prev没问题;在hlist中,表头 没有prev,也没有next,只有一个first。为了能统一地修改表头的first指针,即表头的first指针必须修改指向新插入的节点, hlist就设计了pprev。hlist节点的pprev不再是指向前一个节点的指针,而是指向前一个节点(可能是表头)中的next(对于表头则是 first)指针(struct list_head **pprev),从而在表头插入的操作可以通过一致的“*(node->pprev)”访问和修改前节点的next(或first)指针。
注:pprev是指向前一个节点中的next指针,next是指向hlist_node的指针,所以pprev是一个指向hlist_node的指针的指针。


注意:
pprev可以理解成向list的prev一样,是一个指向hlist_node的指针,又由于hlist_node的第一个元素next是一个指向 hlist_node的指针,pprev也是一个指向next的指针,即pprev是一个指向hlist_node的指针的指针。
struct hlist_node Prev;
struct hlist_node *pprev = (struct hlist_node *) Prev = (struct hlist_node *) (struct hlist_node * next) = struct hlist_node ** next;

下面是hlist中常用的几个宏:
#define HLIST_HEAD_INIT { .first = NULL }
#define HLIST_HEAD(name) struct hlist_head name = { .first = NULL }
#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)
#define INIT_HLIST_NODE(ptr) ((ptr)->next = NULL, (ptr)->pprev = NULL)

下面只列出hlist_add_before操作函数,其他hlist链表操作函数操作方法类似。这个函数中的参数next不能为空。它在next前面加入了n节点。函数的实现与list中对应函数类似。
static inline void __hlist_del(struct hlist_node *n)
{
struct hlist_node *next = n->next;
struct hlist_node **pprev = n->pprev;
*pprev = next;
if (next)
next->pprev = pprev;
}

static inline void hlist_add_before(struct hlist_node *n,struct hlist_node *next)
{
n->pprev = next->pprev;
n->next = next;
next->pprev = &n->next;
*(n->pprev) = n;
}

#define hlist_entry(ptr, type, member) container_of(ptr,type,member)
#define hlist_for_each(pos, head) \
for (pos = (head)->first; pos && ({ prefetch(pos->next); 1; }); \
pos = pos->next)
posted @ 2008-01-17 17:32 lfc 阅读(6888) | 评论 (6)编辑 收藏
     摘要: 注:    写得实在太好了!    原文出处:http://blog.csdn.net/rstevens/archive/2007/10/14/1824785.aspx 1.   概述   根据以前学习内核源码的经验,在学习文件系统实现之前,我大概定了个目标: 1、  建立一个清晰的...  阅读全文
posted @ 2008-01-09 15:49 lfc 阅读(1774) | 评论 (0)编辑 收藏

目录
介绍 
      关于本文档
      更新历史
概念
     术语: devfs, sysfs, nodes, etc.
     为什么?
     内置固定命名设计
编写规则
     规则文件和语义
     规则语法
     基本规则
     sysfs匹配属性
     设备级联结构
     字符串替换
     字符串匹配
从sysfs中查找合适信息
     sysfs树
     udevinfo
     其他方法
高级话题
     权限和所有权控制
     使用外部程序命名设备
     发生特定事件时运行外部程序
     环境交互
     另外选项
例子
     USB打印机
     USB相机
     USB硬盘
     USB读卡器
     USB Palm导航仪
     CD/DVD驱动
     网卡
测试和调试
     让你的规则跑起来
     udevtest
作者及联系方式


介绍
关于本文档

udev面向2.6以上的linux内核在用户空间提供动态的/dev下固定设备命名方案. 之前的/dev实现: devfs现在已被废弃, udev成为继任者. udev vs devfs是一个敏感的谈话内容,在进行比较之前你应该读一下这个文档(http://kernel.org/pub/linux/utils /kernel/hotplug/udev_vs_devfs).

几年间你为之使用udev规则的设备发生改变了,如同规则自身的弹性一样. 在现代系统中udev为系统外的类型设备提供了固定的命名方法, 避免了为这些设备提供定制规则. 但是一些用户仍然需要额外的定制级别.

本文档假设你已经安装了udev并使用缺省配置运行ok. 这通常通过你的linux发行版做到的.

本文档不会覆盖规则书写的方方面面, 只集中介绍所有主要概念. 更多细节信息可以在udev的man页中找到.

本文档使用各种例子(一些完全是虚构的)来阐述观点和概念. 不是所有语法都会显式的在附带文本中描述,请确信通过查看例子规则来获取完整的理解.

更新历史
(略)

概念
语 义: devfs, sysfs, nodes等
仅仅是基本介绍,可能并不完全准确.
在典型的基于linux的系统中,/dev目录用来存储文件一样的设备节点, 它们指向系统中特定的设备. 每一个节点指向系统的一部分(一个设备), 可能存在也可能不存在. 用户空间应用程序可以使用这些设备节点跟系统硬件打交道,例如, X服务器"监听"/dev/input/mice来根据用户的鼠标移动来移动可视鼠标指针.
原来的/dev目录仅仅在设备可能在系统中出现时产生,因此/dev目录一般非常大. 随之而来的devfs提供了一种易于管理的途径(注意它仅仅在硬件插入到系统中时产生/dev)以及其他功能,但系统会出现无法容易修复的问题.

udev是一种新的管理/dev目录的方法,它的设计清除了以前的/dev实现的一些问题并提供了鲁棒的路径向后兼容. 为了创建并命名系统中相应的/dev设备结点,udev需要依赖于根据用户提供的规则从sysfs中得到的匹配信息. 本文着重规则书写的过程,udev相关的任务由用户自己完成.

sysfs是2.6内核中一个新的文件系统, 它由内核管理,并导出当前系统中插入的设备基本信息. udev可使用这些信息创建对应的硬件设备结点. sysfs挂载在/sys下而且是可浏览的. 你可能很希望在使用udev之前刺探下存储在那儿的有关文件. 本文中我将交替使用/sys和sysfs术语.

为什么?
udev规则具有弹性非常强大,这里是一些你使用规则可以达到的结果:
1. 重命名设备节点的缺省名字为其他名字
2. 通过创建符号链接到缺省设备节点来提供一个可选的固定的设备节点名字
3. 基于程序的输出命名设备节点
4. 改变设备节点的权限和所有权
5. 但设备节点被创建或删除时(通常是添加设备或拔出设备时)执行一个脚本
6. 重命名网络接口

当存在的特定设备没有设备节点时,这不是书写规则的工作范围. 即使没有匹配的规则,udev也会利用内核提供的缺省名字来创建设备节点.

拥有固定命名设备节点有很多好处. 假设你有两个USB存储设备:一个数码相机,一个是USB闪存盘. 这些设备通过被赋予/dev/sda和/dev/sdb设备节点,准确的赋值取决于它们连接到系统的顺序. 这可能为一些用户造成麻烦,如果每个设备每次都可以固定命名,比如/dev/camera和/dev/flashdisk,用户就会获益.

内置固定命名方法
udev为系统外的一些设备类型提供了固定命名,这是一个很有用的特征,在某些情况下意味着你不用书写任何规则.

udev为存储设备在/dev/disk目录下提供了系统外命名方法. 要查看它为你的存储硬件创建的固定命名,你可以使用下列命名:
#ls -lR /dev/disk
所有存储类型都可以这么用. 例如udev为我的根分区创建了固定命名链接:/dev/disk/by-id/scsi-SATA_ST3120827AS_4MS1NDXZ- part3. 但我插入我的USB闪存盘udev就会创建另外一个固定命名节点:/dev/disk/by-id/usb- Prolific_Technology_Inc._USB_Mass_Storage_Device-part1.

规则书写
规则文件和语义
为决定如何命名设备以及执行什么另外动作,udev会读取一系列规则文件. 这些文件保存在/etc/udev/rules.d目录下并且都必须有.rules后缀名.

缺省udev规则存储在/etc/udev/rules.d/50-udev.rules里面. 你可能发现整个文件很有意思, 它包含了少量例子,一些缺省规则提供了devfs风格的/dev布局, 但是你不应该直接在这个文件里面书写规则.

/etc/udev/rules.d/下面的文件通过lexical顺序解析,在某些情况下规则的解析顺序很重要. 通常来说你希望你的规则可以在缺省规则之前解析, 所以我建议你创建一个文件/etc/udev/rules.d/10-local.rules并把自己的所有规则写到这里面去.

在一个规则文件中, 以"#"开头的行被认为是注释. 每一个非空的行都是一条规则. 规则不能跨越多行.

一个设备可以被多条规则匹配到, 这有着很实用的优点, 例如, 我们可以写两个匹配同一个设备的规则, 每一个规则为设备提供了它自己的可选命名. 即使分开在不同的文件种, 两个可选命名也都会被创建, 要明白udev在找到一个匹配规则后不会停止处理其他规则, 它仍然会继续查找并尝试应用已知的每条规则, 这很重要.

规则语法
每条规则通过一系列键值对创建,这些键值对通过逗号分隔. 匹配键是用来识别要应用规则的设备的条件, 但规则中对应设备的所有匹配键被处理后,就会应用规则并且赋值键的行为也会触发. 每条规则应该包含至少一个匹配键和至少一个赋值键.

这是用来阐述上面内容的一个例子规则:
  KERNEL=="hdb",NAME="my_spare_disk"
上述规则包含一个匹配键(KERNEL)以及一个赋值键(NAME). 这些键和它们的属性的语义将在稍后具体说明. 注意到匹配键通过连等号(==)与它的值联系起来, 赋值键通过等号(=)与它的值关联.

注意udev不支持任何形式的行连接符, 不要在你的规则种插入任何断行符,这将会导致udev把你的一条规则看做是多条规则但不会按预料工作.

基本规则
udev提供一些用来书写精确匹配规则的匹配键, 其中一些常用键将在下面介绍, 其他将在文档的后面说明. 要得到完整列表可以查看udev的手册页.
KERNEL - 为设备匹配的内核名字
SUBSYSTEM - 匹配设备的子系统
DRIVER - 匹配支持设备的驱动名称
在你使用一系列匹配键来准确匹配设备后,udev通过赋值键为接下来发生的事给你提供更好的控制. 你可以查看udev的手册页查看完整的赋值键列表. 最基本的赋值键在下面说明, 其他的将在文档结束时说明.
NAME - 设备节点应该使用的名字
SYMLINK - 一个设备节点可选名字的符号链接列表
正如之前所说,udev会为设备创建一个真正的设备节点. 如果你希望为设备节点提供可选名字,你得通过SYMLINK使用符号链接功能, 实际上是维护一个符号链接列表,这些符号链接都会指向真实的设备节点. 为了维护这些链接我们介绍一个新的附加操作符: +=. 你可以在一个规则中附加多个符号链接到列表中,每个链接通过空格分开.
 KERNEL=="hdb", NAME="my_spare_disk"
上面规则意思是:匹配一个设备命名为hdb的设备,把它重新命名为my_spare_disk. 设备节点出现在/dev/my_spare_disk.
 KERNEL=="hdb", DRIVER=="ide-disk", SYMLINK+="sparedisk"
上面规则意思是:匹配一个内核命名为hdb以及驱动为ide-disk的设备,命名设备节点为缺省名字并创建一个指向它的sparedisk符号链接。注 意到我们没有指明设备节点名字,于是udev使用缺省名字。 为了保留标准/dev布局,你自己的规则通常没有NAME但会创建一些SYMLINK并/或执行其他赋值操作.
 KERNEL=="hdc", SYMLINK+="cdrom cdrom0"
上面规则很可能就是你要写的典型规则。 它在/dev/cdrom和/dev/cdrom0创建了两个符号链接,都指向/dev/hdc. 再一次地,没有NAME赋值键,所以使用缺省的内核名字(hdc).

匹配sysfs属性
到目前为止介绍的匹配键仅仅提供了有限的匹配能力. 实际上我们需要更加优良的控制:我们想基于设备的高级属性来识别设备, 如供应商编码, 产品编号, 序列号, 存储能力, 分区数等等.

一些驱动导出这些信息到sysfs, udev允许我们使用ATTR键通过稍捎不同的语法来合并sysfs匹配到自己的规则中.

这里有一个匹配sysfs中单个属性例子. 更多细节将在稍后的帮助你基于sysfs属性书写规则的文档中提供. 
 SUBSYSTEM=="block", ATTR{size}=="234441648", SYMLINK+="my_disk"
 
设备级联
linux内核实际上以树状结构展示设备, 这个信息通过sysfs显露出来,在书写规则时这非常有用. 例如我的硬盘设备的展示是一个SCSI磁盘设备的孩子, 这个SCSI磁盘设备又是一个ATA控制器设备的孩子, 该控制器又是PCI总线设备的孩子. 你很有可能发现你需要从一个讨论中的设备的双亲那里引用信息, 比如我的硬盘设备的序列号在设备级别并不暴露出来, 而是在SCSI磁盘级别通过它的直接双亲展现。

目前介绍的四个主要匹配键(KERNEL/SUBSYSTEM/DRIVER/ATTR)仅仅跟对应设备的值匹配, 并不跟双亲设备的值匹配. udev提供了在树中向上查找的匹配键变量:
KERNELS - 为设备匹配的内核名字,或任何双亲设备中的内核名
SUBSYSTEMS - 匹配设备的子系统名,或任何双亲设备中的子系统名
DRIVERS - 匹配支持设备的驱动名,或任何支持双亲设备的驱动名
ATTRS - 匹配设备的sysfs属性,或任何双亲设备的sysfs属性
由于在心里要考虑到级联结构,你可能感觉到规则书写变的有点复杂了. 歇一会吧,有工具可以帮助我们的,稍微献上.

字符串替换
但写的规则潜在的要处理多个相似的设备时, udev的printf-like string substitution operators就非常有用了. 你可以在你的规则里面的任何赋值里面包含这些操作符, udev在它们执行时会计算.

最常用的操作符是%k和%n. %k计算设备的内核名, 例如设备的"sda3"将(缺省)出现在/dev/sda3. %n计算设备(存储设备的分区号)的内核号码, 例如"3"将被换成"/dev/sda3".

udev也提供了一些高级功能替换操作符. 在读完本文剩下内容后可以查询udev的手册页. 以上例子中的操作符也有一个可选的语法 - $kernel和$number. 因此如果你希望在规则中匹配字符%, 你必需写成%%, 如果你希望匹配字符$, 你必须写成$$.

为阐述下字符串替换的概念,我们来展示几个例子:
 KERNEL=="mice", NAME="input/%k"
 KERNEL=="loop0", NAME="loop/%n", SYMLINK+="%k"
第一条规则确保鼠标设备节点一定出现在/dev/input目录下(缺省是在/dev/mice下面). 第二条规则确保名字为loop0的设备节点在/dev/loop/0创建,也会照常创建一个符号链接/dev/loop0.

上面规则的使用都比较可疑,因为他们都可以通过不使用任何替换操作符来重写. 这些替换的真正威力将会在下一节显现.

字符串匹配
不仅有字符串精确匹配, udev也允许你使用shell风格的模式匹配. 支持的3种模式为:
* - 匹配任何字符, 匹配0次或多次
? - 匹配任何字符,但只匹配一次.
[] - 匹配任何单个字符, 这些字符在方括号里面指定, 范围是受限的.
这里有一些例子, 注意字符串替换符的使用:
 KERNEL=="fd[0-9]*", NAME="floppy/%n", SYMLINK+="%k"
 KERNEL=="hiddev*", NAME="usb/%k"
第一条规则匹配所有软盘驱动并确保设备节点放置在/dev/floppy目录下, 也创建一个缺省名字的符号链接. 第二条规则确保hiddev设备节点放在/dev/usb目录下面.

从sysfs中查找信息
sysfs树
从sysfs中获取有意思信息的概念在之前的例子中已经触摸到了. 为了基于这些信息书写规则,你首先需要知道属性名和他们的当前值.

sysfs实际上是一个非常简单的结构,逻辑上以目录形式区分. 每一个目录包含一定量的文件(属性),这些文件往往都仅仅包含一个值. 也会有一些符号链接,它们把设备链到双亲那里, 级联结构已在上面说明了. 

一些目录被引向顶层设备路径,这些目录展示了拥有对应设备节点的实际设备. 顶层设备路径可以被分类为包含dev文件的sysfs目录, 下列命令可以列举出这些文件:
#find /sys -name dev
例如, 在我的系统中, /sys/block/sda目录就是我的硬盘设备路径, 它通过/sys/block/sda/device符号链接链向它的双亲SCSI磁盘设备.

但你书写基于sysfs信息的规则时, 只是简单的匹配链条上一部分文件的属性内容. 例如, 我可以这样读我的硬盘的大小:
#cat /sys/block/sda/size
234441648
在一个udev规则里面, 我可以使用ATTR{size}=="234441648"来识别这个磁盘. 因为udev反复遍历设备链的入口,我可以通过ATTRS匹配另外链中的属性(如:/sys/class/block/sda/device属性), 但是在处理链中不同部分时会有一些告诫, 稍后描述.

虽然这对于关于sysfs的结构和udev如何匹配值的介绍很有用, 但对sysfs的全面梳理是个既耗时也没必要的事.

udevinfo
敲入udevinfo大概就是你用来创建规则的最直接的工具了。你需要知道的全部就是设备的sysfs设备路径. 下面是一个精简的例子:
# udevinfo -a -p /sys/block/sda

  looking at device '/block/sda':
  KERNEL=="sda"
  SUBSYSTEM=="block"
  ATTR{stat}==" 128535 2246 2788977 766188 73998 317300 3132216 5735004 0 516516 6503316"
  ATTR{size}=="234441648"
  ATTR{removable}=="0"
  ATTR{range}=="16"
  ATTR{dev}=="8:0"

  looking at parent device '/devices/pci0000:00/0000:00:07.0/host0/target0:0:0/0:0:0:0':
  KERNELS=="0:0:0:0"
  SUBSYSTEMS=="scsi"
  DRIVERS=="sd"
  ATTRS{ioerr_cnt}=="0×0"
  ATTRS{iodone_cnt}=="0×31737"
  ATTRS{iorequest_cnt}=="0×31737"
  ATTRS{iocounterbits}=="32"
  ATTRS{timeout}=="30"
  ATTRS{state}=="running"
  ATTRS{rev}=="3.42"
  ATTRS{model}=="ST3120827AS "
  ATTRS{vendor}=="ATA "
  ATTRS{scsi_level}=="6"
  ATTRS{type}=="0"
  ATTRS{queue_type}=="none"
  ATTRS{queue_depth}=="1"
  ATTRS{device_blocked}=="0"

  looking at parent device '/devices/pci0000:00/0000:00:07.0':
  KERNELS=="0000:00:07.0"
  SUBSYSTEMS=="pci"
  DRIVERS=="sata_nv"
  ATTRS{vendor}=="0×10de"
  ATTRS{device}=="0×037f"
正如你看到的, udevinfo简单的产生一个你可以在udev规则中作为匹配键的属性列表. 从上面例子中我可以为该设备产生下面两条规则:
SUBSYSTEM=="block", ATTR{size}=="234441648", NAME="my_hard_disk"
SUBSYSTEM=="block", SUBSYSTEMS=="scsi", ATTRS{model}=="ST3120827AS", NAME="my_hard_disk"
你可能发现到例子中使用了颜色, 这是为了阐述把设备属性和单个双亲设备属性并在一起是合法的, 你不能混合匹配多个双亲设备属性,这样你的规则不能工作. 例如,下列规则是不合理的,因为它试图匹配两个双亲设备属性:
SUBSYSTEM=="block", ATTRS{model}=="ST3120827AS", DRIVERS=="sata_nv", NAME="my_hard_disk"
通常有大量的属性提供给你,你必需挑选其中一些来创建你的规则. 一般来讲,你希望挑选那些能够固定标志你的设备并便于理解的属性. 上例中我选取的是我的磁盘大小和它的模型号,而没有使用没有意义的诸如ATTRS{iodone_cnt}=="0×31737"数字.

仔细观察下udevinfo的输出级联结构效果, 设备的绿色部分使用了标准匹配键如KERNEL和ATTR, 蓝色部分和栗色部分使用了双亲遍历变量如SUBSYSTEMS和ATTRS, 这就是为什么级联结构的复杂性实际上是很容易处理的原因,只要确保使用udevinfo建议的准确值就行了.

另外一点需要指出的是udevinfo输出的文本属性之间用空格填充(例如上面的ST3120827AS)是很常见的. 在你的规则中你可以添加额外的空格,也可以像我那样去掉.

使用udevinfo的唯一复杂之处在于要求你知道顶级设备路径(例如上面例子中的/sys/block/sda), 这通常并不明显. 但是, 因为你通常是为已经存在的设备节点写规则, 你可以自己使用udevinfo查找设备路径:
#udevinfo -a -p $(udevinfo -q path -n /dev/sda)

可选方法
虽然udevinfo差不多是列举你要构建的规则的准确属性的最直接方式, 但一些用户仍然乐于使用其他工具, 诸如usbview的工具可以显示类似的信息集, 这些信息也可以用在规则中.

高级话题
控制权限和所有权
udev允许你在规则中使用另外的赋值来控制每个设备的所有权和权限属性.

GROUP赋值允许你定义哪个Unix组应该拥有设备节点. 这里有一个例子规则, 它定义video组拥有framebuffer设备:
KERNEL=="fb[0-9]*", NAME="fb/%n", SYMLINK+="%k", GROUP="video"
OWNER键可能用处不大, 它允许你定义哪个Unix用户应该具有设备节点的拥有权限. 假设有个临时情况要你让join拥有软盘设备,你可以使用:
KERNEL="fd[0-9]*", OWNER="join"
udev缺省用Unix的0660权限(拥有者和组员拥有读写功能)创建设备节点. 需要的话你可以在特定设备的规则中使用包含MODE赋值键覆盖这些缺省值. 作为例子,下面的规则定义了inotify节点可以被每个人读写:
KERNEL=="inotify", NAME="misc/%k", SYMLINK+="%k", MODE="0666"

使用外部程序来命名设备
某些情况下你可能要求比udev标准规则提供的更多弹性, 这种情况下你可以请求udev运行一个程序并运用程序的标准输出来提供设备命名.

要使用这个功能,你只需简单的在PROGRAM赋值中指定要运行程序(以及任何阐述)的完整路径, 然后在NAME/SYMLINK赋值中使用一些%c替换.

下列例子引用一个位于/bin/device_namer的虚构程序. device_namer带一个表示内核名字的命令行参数, 基于内核名device_namer做一些魔幻变换接着产生一些输出到普通stdout管道,这些输出被分割为很多小块, 每一小块是一个单词,块之间用一个空格分开.

在我们的第一个例子中, 我们假设device_namer输出块的数目, 每一个形成当前设备的一个符号链接(名字可选).
KERNEL=="hda", PROGRAM="/bin/device_namer %k", SYMLINK+="%c"
下一个例子假设device_namer输出两块,第一块是设备名字, 第二块是另外的符号链接名字. 我们现在介绍%c[N]替换, 它引向输出的第N块:
KERNEL=="hda", PROGRAM="/bin/device_namer %k", NAME="%c{1}", SYMLINK+="%c{2}"
再下个例子假设device_namer输出设备名的一部分, 后面跟着的是快数, 它将形成另外的符号链接. 我们现在介绍%c{N+}替换, 它将被计算为块N, N+1, N+2…直到输出结束.
KERNEL=="hda", PROGRAM="/bin/device_namer %k", NAME="%c{1}", SYMLINK+="%c{2+}"
输出块也可在赋值键中使用,而不仅仅是NAME和SYMLINK中. 下面例子使用一个虚构程序来决定哪个Unix组拥有设备:
KERNEL=="hda", PROGRAM="/bin/who_owns_device %k", GROUP="%c"

特定事件发生时运行外部程序
另外一个书写udev规则的原因是为了在设备连接或者断开时运行一个特定程序. 例如, 你可能想在你的数码相机连到系统时执行一个脚本来自动下载相机里面的所有照片.

不要把这个跟上述的PROGRAM功能弄混淆了, PROGRAM是用来运行产生设备名字(除此之外不应该做其他事情)的程序. 但这些程序执行时, 设备节点设备节点还没有被创建, 所以对设备的任何形式的操作都是不可能的.

这里介绍的功能允许你在设备节点到位后运行一个程序. 该程序可以作用在设备上, 但是它不准在时间周期外运行,因为当程序运行时udev会正常中止. 这个限制的一个权宜之计是确保你的程序立即分离自身.

这里有个展示RUN赋值的例子:
KERNEL=="sdb", RUN+="/usr/bin/my_program"
当/usr/bin/my_program执行时, udev环境的各部分可作为环境变量可用,包括诸如SUBSYSTEM的键值. 你也可以使用ACTION环境变量来检测设备是否连接或断开, 它的值是"add"和"remove"其中的一个.

udev并不在任何激活终端中运行这些程序,也不再shell上下文中执行. 确信你的程序是被标记为可执行的, 如果它是个shell脚本请确保它以适当的shabang开头(比如: #!/bin/sh)也不要期望任何标准输出出现在你的终端上.

环境交互
udev为环境变量提供了一个ENV键, 即可用来匹配也可用来赋值.

在赋值情况下,你可以设置稍后匹配的环境变量. 你也可以设置环境变量,供通过上面提供的技巧触发的任何外部程序使用. 一个虚构的设置环境变量的例子规则如下:
KERNEL=="fd0", SYMLINK+="floppy", ENV{some_var}="value"
在匹配情况下你可以确保规则仅仅依赖一个环境变量的值而运行. 注意udev看到的环境跟你在控制台上得到的用户环境不一样. 一个虚构的涉及环境匹配的规则如下:
KERNEL=="fd0", ENV{an_env_var}=="yes", SYMLINK+="floppy"
上面规则仅仅在udev环境中的$an_env_var的值设为"yes"时才创建/dev/floppy链接.

另外选项
另外一个有用的赋值是OPTIONS列表. 不多的可用的选项有:
all_partitions - 为一个块设备创建所有可能的分区, 而不是初始检测到的那些分区.
ignore_device - 完全忽略事件.
last_rule - 确保后面的所有规则不会有效.
例如, 下面规则设置我的硬盘节点的组所有权,并且后面的规则对它没有任何效果:
KERNEL=="sda", GROUP="disk", OPTIONS+="last_rule"

例子
USB 打印机
我启动我的打印机, 它就被赋予了一个设备节点/dev/lp0. 我对这样的单调的名字不满意并打算使用udevinfo帮我写一个规则来提供一个可选名字:
# udevinfo -a -p $(udevinfo -q path -n /dev/lp0)
  looking at device '/class/usb/lp0':
  KERNEL=="lp0"
  SUBSYSTEM=="usb"
  DRIVER==""
  ATTR{dev}=="180:0"

  looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb1/1-1':
  SUBSYSTEMS=="usb"
  ATTRS{manufacturer}=="EPSON"
  ATTRS{product}=="USB Printer"
  ATTRS{serial}=="L72010011070626380"
我的规则变成了这样:
SUBSYSTEM=="usb", ATTRS{serial}=="L72010011070626380", SYMLINK+="epson_680"
   
USB相机
跟大多数相机一样, 我的相机标识自己为一个通过USB总线来连接通过SCSI传输的外部硬盘. 为了访问我的相片我先挂载驱动然后拷贝图片文件到我的硬盘中.

不是所有相机都这样工作, 其中一些使用非存储协议,如gphoto2支持的相机. 在gphoto情况下,你不用为你的设备写任何规则,因为纯粹由用户空间控制(而不是指定的内核驱动).

USB相机设备的一个通常的复杂性在于它们通常标识自己是一个单分区磁盘,即带有/dev/sdb1的/dev/sdb. sdb节点对我毫无用处,但对sdb1有兴趣 - 这才是我要挂载的. 这里有个麻烦因为sysfs是链式的, udevinfo为/dev/sdb1产生的有用属性跟为/dev/sdb产生的一样, 这导致你的规则潜在的匹配到原始磁盘和分区, 这不是你想要的, 你的规则应该明确下.

为解决这个问题,你需要简单的考虑下sdb和sdb1之间有什么区别. 令人惊奇的简单: 只是名字上的区别, 所以我们可在NAME域上使用一个简单的模式匹配.
# udevinfo -a -p $(udevinfo -q path -n /dev/sdb1)
  looking at device '/block/sdb/sdb1':
  KERNEL=="sdb1"
  SUBSYSTEM=="block"

  looking at parent device '/devices/pci0000:00/0000:00:02.1/usb1/1-1/1-1:1.0/host6/target6:0:0/6:0:0:0':
  KERNELS=="6:0:0:0"
  SUBSYSTEMS=="scsi"
  DRIVERS=="sd"
  ATTRS{rev}=="1.00"
  ATTRS{model}=="X250,D560Z,C350Z"
  ATTRS{vendor}=="OLYMPUS "
  ATTRS{scsi_level}=="3"
  ATTRS{type}=="0"

我的规则: 
KERNEL=="sd?1", SUBSYSTEMS=="scsi", ATTRS{model}=="X250,D560Z,C350Z", SYMLINK+="camera"

USB硬盘
USB硬盘跟USB相机差不多,但典型的使用方式不同. 在相机例子中, 我有讲到我对sdb节点没有兴趣, 它仅仅在分区(比如使用fdisk)时才有用, 但我为什么要为我的相机分区呢?

当然如果你有一个100GB的USB硬盘, 你希望为它分区是很好理解的, 这种情况下我们可以使用udev的字符串替换长处:
KERNEL=="sd*", SUBSYSTEM=="scsi", ATTRS{model}=="USB 2.0 Storage Device", SYMLINK+="usbhd%n"
这个规则创建诸如下面的符号链接:
/dev/usbhd - 可被fdisk使用的node
/dev/usbhd1 - 第一块分区(可挂载)
/dev/usbhd2 - 第二块分区(可挂载)

USB读卡器
USB读卡器(CompactFlash, SmartMedia等)属于USB存储设备的另外一种范围, 它有不同的使用需求.

这些设备特别的在媒介改变时不用通知主机. 所以如果你插入没有媒介的设备,接着插入一张卡, 计算机不会意识到这点, 你也不会有媒介的可挂载的sdb1分区节点.

一个可能的解决办法是使用all_partttions选项优点, 它将为每个规则匹配的块设备创建16个分区节点:
KERNEL="sd*", SUBSYSTEM=="scsi", ATTRS{model}=="USB 2.0 CompactFlash Reader", SYMLINK+="cfrdr%n", OPTIONS+="all_partitions"
你将得到这些命名节点:cfrdr, cfrdr1, cfrdr2, cfrdr3, …, cfrdr15.

USB Palm导航仪
这些设备作为USB串行设备工作, 所以缺省的你仅仅得到ttyUSB1设备节点. palm工具依赖于/dev/pilot, 一些用户希望使用一条规则提供这个.

Carsten Clasohm的博客上有完整的源. Carsten的规则如下:
SUBSYSTEM=="usb", ATTRS{product}=="Palm Handheld", KERNEL=="ttyUSB*", SYMLINK+="pilot"
注意到产品字符串因产品不同而不同, 所以确保你检查(使用udevinfo)了哪一个可以应用到你自己的产品.

CD/VCD驱动
我的电脑有两个光驱: 一个DVD只读驱动(hdc)和一个DVD刻录机(hdd). 我不希望这些设备节点改变除非我物理性的重新接线, 但是一些用户喜欢拥有诸如/dev/dvd这么方便的设备节点.

我们都知道KERNEL是这些设备的名字, 规则书写就很简单了. 这是我的系统上一些规则:
SUBSYSTEM=="block", KERNEL=="hdc", SYMLINK+="dvd", GROUP="cdrom"
SUBSYSTEM=="block", KERNEL=="hdd", SYMLINK+="dvdrw", GROUP="cdrom"

网卡
尽管它们都是通过名字引用,网卡往往没有与之关联的设备节点. 尽管这样,规则书写过程还是相同的.

在规则中简单的匹配网卡MAC地址是有意义的,因为它们是唯一的. 但是, 确信你使用的是udevinfo显示的准确MAC地址, 因为如果你没有精确匹配,你的规则不会工作.
# udevinfo -a -p /sys/class/net/eth0
  looking at class device '/sys/class/net/eth0':
  KERNEL=="eth0"
  ATTR{address}=="00:52:8b:d5:04:48"
这是我的规则:
KERNEL=="eth*", ATTR{address}=="00:52:8b:d5:04:48", NAME="lan"
为了让这个规则生效你得重启网络驱动, 你可以卸载并重新加载模块或简单的重启系统即可. 你还得需要重新配置你的系统使用"lan"取代"eth0". 我以前遇到过这样的麻烦(网卡不能重命名)直到我去除了所有对eth0的引用. 在此之后你应该可以在任何ifconfig或类似的工具的调用中使用"lan"而不是"eth0"了.

测 试和调试
让 你的规则跑起来
假定你在一个有inotify支持的最近内核上工作, udev将自动监视你的规则目录并且自动挑取你对规则文件的任何修改.

尽管这样, udev也不会自动重新处理所有设备并试图应用新规则. 例如, 如果你写了个规则来为你的已连接相机添加一个额外符号链接, 你不能指望这个符号链接可以马上显现出来.

为使符号链接显示, 你可以断开并重连你的相机或者在非可移除设备情况下运行udevtrigger.

如果你的内核没有inotify支持, 新规则不会自动被检测到. 这种情况下你必需在做出更改后运行udevcontrol reload_rules使之生效.

udevtest
如果你知道sysfs中的顶级设备路径, 你可以使用udevtest来显示udev将要执行的动作, 这可能会帮你调试你的规则. 例如, 假设你想调试作用在/sys/class/sound/dsp上的规则:
# udevtest /class/sound/dsp
main: looking at device '/class/sound/dsp' from subsystem 'sound'
udev_rules_get_name: add symlink 'dsp'
udev_rules_get_name: rule applied, 'dsp' becomes 'sound/dsp'
udev_device_event: device '/class/sound/dsp' already known, remove possible symlinks
udev_node_add: creating device node '/dev/sound/dsp', major = '14', minor = '3', mode = '0660', uid = '0', gid = '18'
udev_node_add: creating symlink '/dev/dsp' to 'sound/dsp'
注意/sys前缀在udevtest命令行中被删除了, 这是因为udevtest在设备路径上操作. 还要留意的是udevtest是一个纯粹的测试/调试工具, 它不创建任何设备节点无论输出怎么建议.

作 者以及联系方式
本文由Daniel Drake<dan@reactivated.net>写就. 欢迎反馈信息.

对于技术支持你可以致信给linux-hotplug邮件列表: linux-hotplug-devel@lists.sourceforge.net

Copyright (C) 2003-2006 Daniel Drake.
This document is licensed under the GNU General Public License, Version 2.




posted @ 2007-12-18 16:10 lfc 阅读(16576) | 评论 (2)编辑 收藏
#####################################################################################################
早前曾研究了一下输入子系统的原理,给人的感觉是输入子系统很复杂.但其实内核开发者在这方面已经做得很完善了,
输入子系统虽然错综复杂,但是只要我们领会了输入子系统的一些设计思想后,我们要使用它并非难事.

以下以内核自带的gpio_keys驱动为例,介绍输入子系统的使用.
主要的原因是gpio_keys驱动比较简单易懂,另外不是没个人都有触摸屏,但键盘的话相信每一块开发板上都配有吧^_^

按照以前的习惯,先从下到上的研究底层驱动是如何提交输入事件的:
#####################################################################################################

drivers/input/keyboard/gpio_keys.c:

static int __devinit gpio_keys_probe(struct platform_device *pdev)
{
    struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;
    struct input_dev *input;
    int i, error;

    input = input_allocate_device();//申请input_dev结构
    if (!input)
        return -ENOMEM;

    platform_set_drvdata(pdev, input);//把input_dev结构放好(以后方便调用)

    input->evbit[0] = BIT(EV_KEY);//目前event的类型不操作32,所以你会看到对于evbit数组的操作都是对evbit[0]中的位来进行操作.

    input->name = pdev->name;
    input->phys = "gpio-keys/input0";
    input->dev.parent = &pdev->dev;

    input->id.bustype = BUS_HOST;
    input->id.vendor = 0x0001;
    input->id.product = 0x0001;
    input->id.version = 0x0100;

    for (i = 0; i < pdata->nbuttons; i++) {
        struct gpio_keys_button *button = &pdata->buttons[i];
        int irq = gpio_to_irq(button->gpio);
        unsigned int type = button->type ?: EV_KEY;

        set_irq_type(irq, IRQ_TYPE_EDGE_BOTH);

        /* 根据用户所指定的gpio_keys来申请中断和注册中断处理函数*/
        error = request_irq(irq, gpio_keys_isr, IRQF_SAMPLE_RANDOM,
                     button->desc ? button->desc : "gpio_keys",
                     pdev);
        if (error) {
            printk(KERN_ERR "gpio-keys: unable to claim irq %d; error %d\n",
                irq, error);
            goto fail;
        }

        input_set_capability(input, type, button->code);
    }

    error = input_register_device(input);//注册输入设备,并和对应的handler处理函数挂钩
    if (error) {
        printk(KERN_ERR "Unable to register gpio-keys input device\n");
        goto fail;
    }

    return 0;

 fail:
    for (i = i - 1; i >= 0; i--)
        free_irq(gpio_to_irq(pdata->buttons[i].gpio), pdev);

    input_free_device(input);

    return error;
}


提到input_dev结构,以下谈一下我对于它的理解:
struct input_dev {

    void *private;

    const char *name;
    const char *phys;
    const char *uniq;
    struct input_id id;

    /*
     * 根据各种输入信号的类型来建立类型为unsigned long 的数组,
     * 数组的每1bit代表一种信号类型,
     * 内核中会对其进行置位或清位操作来表示时间的发生和被处理.
     */

    unsigned long evbit[NBITS(EV_MAX)];
    unsigned long keybit[NBITS(KEY_MAX)];
    unsigned long relbit[NBITS(REL_MAX)];
    unsigned long absbit[NBITS(ABS_MAX)];
    unsigned long mscbit[NBITS(MSC_MAX)];
    unsigned long ledbit[NBITS(LED_MAX)];
    unsigned long sndbit[NBITS(SND_MAX)];
    unsigned long ffbit[NBITS(FF_MAX)];
    unsigned long swbit[NBITS(SW_MAX)];

    .........................................
};

/**
 * input_set_capability - mark device as capable of a certain event
 * @dev: device that is capable of emitting or accepting event
 * @type: type of the event (EV_KEY, EV_REL, etc...)
 * @code: event code
 *
 * In addition to setting up corresponding bit in appropriate capability
 * bitmap the function also adjusts dev->evbit.
 */

/* 记录本设备对于哪些事件感兴趣(对其进行处理)*/
void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code)
{
    switch (type) {
    case EV_KEY:
        __set_bit(code, dev->keybit);//比如按键,应该对哪些键值的按键进行处理(对于其它按键不予理睬)
        break;

    case EV_REL:
        __set_bit(code, dev->relbit);
        break;

    case EV_ABS:
        __set_bit(code, dev->absbit);
        break;

    case EV_MSC:
        __set_bit(code, dev->mscbit);
        break;

    case EV_SW:
        __set_bit(code, dev->swbit);
        break;

    case EV_LED:
        __set_bit(code, dev->ledbit);
        break;

    case EV_SND:
        __set_bit(code, dev->sndbit);
        break;

    case EV_FF:
        __set_bit(code, dev->ffbit);
        break;

    default:
        printk(KERN_ERR
            "input_set_capability: unknown type %u (code %u)\n",
            type, code);
        dump_stack();
        return;
    }

    __set_bit(type, dev->evbit);//感觉和前面重复了(前面一经配置过一次了)
}
EXPORT_SYMBOL(input_set_capability);


static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
{
        int i;
        struct platform_device *pdev = dev_id;
        struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;
        struct input_dev *input = platform_get_drvdata(pdev);

        for (i = 0; i < pdata->nbuttons; i++) {
                struct gpio_keys_button *button = &pdata->buttons[i];
                int gpio = button->gpio;

                if (irq == gpio_to_irq(gpio)) {//判断哪个键被按了?
                        unsigned int type = button->type ?: EV_KEY;
                        int state = (gpio_get_value(gpio) ? 1 : 0) ^ button->active_low;//记录按键状态

                        input_event(input, type, button->code, !!state);//汇报输入事件
                        input_sync(input);//等待输入事件处理完成
                }
        }

        return IRQ_HANDLED;
}


/*
 * input_event() - report new input event
 * @dev: device that generated the event
 * @type: type of the event
 * @code: event code
 * @value: value of the event
 *
 * This function should be used by drivers implementing various input devices
 * See also input_inject_event()
 */
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
    struct input_handle *handle;

    if (type > EV_MAX || !test_bit(type, dev->evbit))//首先判断该事件类型是否有效且为该设备所接受
        return;

    add_input_randomness(type, code, value);

    switch (type) {

        case EV_SYN:
            switch (code) {
                case SYN_CONFIG:
                    if (dev->event)
                        dev->event(dev, type, code, value);
                    break;

                case SYN_REPORT:
                    if (dev->sync)
                        return;
                    dev->sync = 1;
                    break;
            }
            break;

        case EV_KEY:
            /*
             * 这里需要满足几个条件:
             * 1: 键值有效(不超出定义的键值的有效范围)
             * 2: 键值为设备所能接受(属于该设备所拥有的键值范围)
             * 3: 按键状态改变了
             */

            if (code > KEY_MAX || !test_bit(code, dev->keybit) || !!test_bit(code, dev->key) == value)
                return;

            if (value == 2)
                break;

            change_bit(code, dev->key);//改变对应按键的状态

            /* 如果你希望按键未释放的时候不断汇报按键事件的话需要以下这个(在简单的gpio_keys驱动中不需要这个,暂时不去分析) */
            if (test_bit(EV_REP, dev->evbit) && dev->rep[REP_PERIOD] && dev->rep[REP_DELAY] && dev->timer.data && value) {
                dev->repeat_key = code;
                mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->rep[REP_DELAY]));
            }

            break;
........................................................

    if (type != EV_SYN)
        dev->sync = 0;

    if (dev->grab)
        dev->grab->handler->event(dev->grab, type, code, value);
    else
        /*
         * 循环调用所有处理该设备的handle(event,mouse,ts,joy等),
         * 如果有进程打开了这些handle(进行读写),则调用其对应的event接口向气汇报该输入事件.
         */
        list_for_each_entry(handle, &dev->h_list, d_node)
            if (handle->open)
                handle->handler->event(handle, type, code, value);
}
EXPORT_SYMBOL(input_event);


#########################################################################
好了,下面再来研究一下event层对于input层报告的这个键盘输入事件是如何来处理的.
#########################################################################

drivers/input/evdev.c:

static struct input_handler evdev_handler = {
        .event =        evdev_event,
        .connect =      evdev_connect,
        .disconnect =   evdev_disconnect,
        .fops =         &evdev_fops,
        .minor =        EVDEV_MINOR_BASE,
        .name =         "evdev",
        .id_table =     evdev_ids,
};

static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
{
        struct evdev *evdev = handle->private;
        struct evdev_client *client;

        if (evdev->grab) {
                client = evdev->grab;

                do_gettimeofday(&client->buffer[client->head].time);
                client->buffer[client->head].type = type;
                client->buffer[client->head].code = code;
                client->buffer[client->head].value = value;
                client->head = (client->head + 1) & (EVDEV_BUFFER_SIZE - 1);

                kill_fasync(&client->fasync, SIGIO, POLL_IN);
        } else
                  /* 遍厉client_list链表中的client结构(代表些打开evdev的进程(个人理解^_^)) */
                list_for_each_entry(client, &evdev->client_list, node) {
                            /* 填充代表该输入信号的struct input_event结构(事件,类型,键码,键值) */
                        do_gettimeofday(&client->buffer[client->head].time);
                        client->buffer[client->head].type = type;
                        client->buffer[client->head].code = code;
                        client->buffer[client->head].value = value;
                            /* 更新写指针 */
                        client->head = (client->head + 1) & (EVDEV_BUFFER_SIZE - 1);

                        kill_fasync(&client->fasync, SIGIO, POLL_IN);//通知调用input_sync的进程:输入事件经已处理完毕(通知底层).
                }

        wake_up_interruptible(&evdev->wait);//唤醒睡眠在evdev->wait等待队列等待输入信息的进程(通知上层).
}

###################################################################################
好了,至此一个按键的输入事件处理完毕,现在再来从上到上的来看看用户是如何获取这个输入事件的.
###################################################################################


static const struct file_operations evdev_fops = {
        .owner =        THIS_MODULE,
        .read =         evdev_read,
        .write =        evdev_write,
        .poll =         evdev_poll,
        .open =         evdev_open,
        .release =      evdev_release,
        .unlocked_ioctl = evdev_ioctl,
#ifdef CONFIG_COMPAT
        .compat_ioctl = evdev_ioctl_compat,
#endif
        .fasync =       evdev_fasync,
        .flush =        evdev_flush
};


static int evdev_open(struct inode *inode, struct file *file)
{
        struct evdev_client *client;
        struct evdev *evdev;
        int i = iminor(inode) - EVDEV_MINOR_BASE;
        int error;

        if (i >= EVDEV_MINORS)
                return -ENODEV;

        evdev = evdev_table[i];

        if (!evdev || !evdev->exist)
                return -ENODEV;

        client = kzalloc(sizeof(struct evdev_client), GFP_KERNEL);
        if (!client)
                return -ENOMEM;

        client->evdev = evdev;
         /* 添加evdev_client结构到链表evdev->client_list中(好让输入事件到来的时候填写该结构并唤醒进程读取) */
        list_add_tail(&client->node, &evdev->client_list);

        if (!evdev->open++ && evdev->exist) {
                error = input_open_device(&evdev->handle);
                if (error) {
                        list_del(&client->node);
                        kfree(client);
                        return error;
                }
        }

        file->private_data = client;//存放好evdev_client结构方便以后使用
        return 0;
}


static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
{
        struct evdev_client *client = file->private_data;
        struct evdev *evdev = client->evdev;
        int retval;

        if (count < evdev_event_size())//对于每次读取的数据大小是有一定的要求.
                return -EINVAL;

        if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))//缓存中没有数据可读且设备是存在的,
                                                      如果设置为NONBLOCK方式来读,立即返回.
                return -EAGAIN;

        retval = wait_event_interruptible(evdev->wait,
                client->head != client->tail || !evdev->exist);//否则等待缓存有数据可读或设备不存在(被移去)
        if (retval)
                return retval;

        if (!evdev->exist)
                return -ENODEV;

        while (client->head != client->tail && retval + evdev_event_size() <= count) {//下面开始读取数据

                struct input_event *event = (struct input_event *) client->buffer + client->tail;//获取缓存中的读指针

                if (evdev_event_to_user(buffer + retval, event))//返回数据给用户
                        return -EFAULT;

                client->tail = (client->tail + 1) & (EVDEV_BUFFER_SIZE - 1);//更新读指针
                retval += evdev_event_size();
        }

        return retval;
}

呵呵,看到了吧,应用程序就是这样获取输入事件的^_^

######################################################################################################################################
本来对于gpio_keys这样的驱动程序,只要当发生按键事件的时候向上层应用程序汇报键值即可.
不过,对于一些带输出设备(例如led灯)的输入设备来说(例如键盘),上层应用程序同样可以利用event层来读取或改变其状态.
请看以下代码:
######################################################################################################################################

static ssize_t evdev_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos)
{
        struct evdev_client *client = file->private_data;
        struct evdev *evdev = client->evdev;
        struct input_event event;
        int retval = 0;

        if (!evdev->exist)
                return -ENODEV;

        while (retval < count) {

                if (evdev_event_from_user(buffer + retval, &event))//从用户处获取事件结构
                        return -EFAULT;
                input_inject_event(&evdev->handle, event.type, event.code, event.value);//往底层发送事件
                retval += evdev_event_size();
        }

        return retval;
}


/**
 * input_inject_event() - send input event from input handler
 * @handle: input handle to send event through
 * @type: type of the event
 * @code: event code
 * @value: value of the event
 *
 * Similar to input_event() but will ignore event if device is "grabbed" and handle
 * injecting event is not the one that owns the device.
 */
void input_inject_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
{
        if (!handle->dev->grab || handle->dev->grab == handle)
                input_event(handle->dev, type, code, value);
}
EXPORT_SYMBOL(input_inject_event);

/*
 * input_event() - report new input event
 * @dev: device that generated the event
 * @type: type of the event
 * @code: event code
 * @value: value of the event
 *
 * This function should be used by drivers implementing various input devices
 * See also input_inject_event()
 */
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
    struct input_handle *handle;

    if (type > EV_MAX || !test_bit(type, dev->evbit))//首先判断该事件类型是否有效且为该设备所接受
        return;

    add_input_randomness(type, code, value);

    switch (type) {

        case EV_SYN:
            switch (code) {
                case SYN_CONFIG:
                    if (dev->event)
                        dev->event(dev, type, code, value);
                    break;

                case SYN_REPORT:
                    if (dev->sync)
                        return;
                    dev->sync = 1;
                    break;
            }
            break;

.............................................................
        case EV_LED:

            if (code > LED_MAX || !test_bit(code, dev->ledbit) || !!test_bit(code, dev->led) == value)
                return;

            change_bit(code, dev->led);

            if (dev->event)
                dev->event(dev, type, code, value);

            break;


    if (type != EV_SYN)
        dev->sync = 0;

    if (dev->grab)
        dev->grab->handler->event(dev->grab, type, code, value);
    else
        /*
         * 循环调用所有处理该设备的handle(event,mouse,ts,joy等),
         * 如果有进程打开了这些handle(进行读写),则调用其对应的event接口向气汇报该输入事件.
         */
        list_for_each_entry(handle, &dev->h_list, d_node)
            if (handle->open)
                handle->handler->event(handle, type, code, value);
}
EXPORT_SYMBOL(input_event);

注:
    鉴于简单的gpio_keys驱动中没有注册自己的event接口,当然也没有对于LED灯的处理,而event层只是简单的向上层汇报输入事件(event层也不可能帮你处理你的led设备,对吧),所以这个通过输入子系统控制LED的部分暂时不去研究.
    (输出设备LED灯不属于这个输入设备gpio_key的一部分.当然,如果你想通过这个gpio_keys设备来控制led灯的话,可以修改这个gpio_keys驱动,详细可参考driver/input/keyboard目录下的驱动)









posted @ 2007-11-12 09:01 lfc 阅读(15416) | 评论 (7)编辑 收藏
     摘要: int __init s3c2410fb_probe(struct device *dev){    struct s3c2410fb_info *info;    struct fb_info       *fbinfo;    struct platfor...  阅读全文
posted @ 2007-10-30 10:31 lfc 阅读(12034) | 评论 (3)编辑 收藏
上文从下到上的介绍了spi子系统,现在反过来从上到下的来介绍spi子系统的使用:
int spi_register_driver(struct spi_driver *sdrv)
{
        sdrv->driver.bus = &spi_bus_type;
        if (sdrv->probe)
                sdrv->driver.probe = spi_drv_probe;
        if (sdrv->remove)
                sdrv->driver.remove = spi_drv_remove;
        if (sdrv->shutdown)
                sdrv->driver.shutdown = spi_drv_shutdown;
        return driver_register(&sdrv->driver);
}

后记:
static int spi_drv_probe(struct device *dev)
{
    const struct spi_driver *sdrv = to_spi_driver(dev->driver);

    return sdr->probe(to_spi_device(dev));
}

然后你就可以在该spi设备驱动的probe函数里面进行必要的初始化工作了。


2.6内核的典型做法,不直接使用原始设备驱动,而是使用包装后的抽象设备驱动spi_driver,
间接与原始设备驱动建立联系,并最终通过调用driver_register来注册原始设备驱动(要充分理解2.6内核的抽象化思想)。
注:
    以后我们也不会直接与原始设备打交道了,而是通过spi_device来间接操作spi设备了^_^

/**
 * spi_write_then_read - SPI synchronous write followed by read
 * @spi: device with which data will be exchanged
 * @txbuf: data to be written (need not be dma-safe)
 * @n_tx: size of txbuf, in bytes
 * @rxbuf: buffer into which data will be read
 * @n_rx: size of rxbuf, in bytes (need not be dma-safe)
 *
 * This performs a half duplex MicroWire style transaction with the
 * device, sending txbuf and then reading rxbuf.  The return value
 * is zero for success, else a negative errno status code.
 * This call may only be used from a context that may sleep.
 *
 * Parameters to this routine are always copied using a small buffer;
 * performance-sensitive or bulk transfer code should instead use
 * spi_{async,sync}() calls with dma-safe buffers.
 */

/*
 * spi_write_then_read比较简单,容易说明spi的使用,用它来作例子比较合适
 */

int spi_write_then_read(struct spi_device *spi,
                const u8 *txbuf, unsigned n_tx,
                u8 *rxbuf, unsigned n_rx)
{
        static DECLARE_MUTEX(lock);

        int                     status;
        struct spi_message      message;
        struct spi_transfer     x[2];
        u8                      *local_buf;

        /* Use preallocated DMA-safe buffer.  We can't avoid copying here,
         * (as a pure convenience thing), but we can keep heap costs
         * out of the hot path ...
         */
        if ((n_tx + n_rx) > SPI_BUFSIZ)//SPI_BUFSIZ == 32
                return -EINVAL;

     /* 这里初始化message结构里面用于存放struct spi_transfer指针的链表头 */
        spi_message_init(&message);//INIT_LIST_HEAD(&message->transfers);
        memset(x, 0, sizeof x);
     /* 留意到没有:tx和rx个占一个工作添加到message的struct spi_transfer链表里,稍后被bitbang_work从链表里提出来处理(后面会讲到) */
        if (n_tx) {
                x[0].len = n_tx;
                spi_message_add_tail(&x[0], &message);//list_add_tail(&t->transfer_list, &m->transfers);
        }
        if (n_rx) {
                x[1].len = n_rx;
                spi_message_add_tail(&x[1], &message);
        }

        /* ... unless someone else is using the pre-allocated buffer */
        /* 如果有人在用这个预分配的缓存,那没办法了,只能再分配一个临时的,用完再释放掉 */
        if (down_trylock(&lock)) {
                local_buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);
                if (!local_buf)
                        return -ENOMEM;
        } else
                local_buf = buf;//否则就采用预分配的缓存吧

        /* local_buf的前部分用来存放要发送的数据,后部分用来存放接收到的数据 */
        memcpy(local_buf, txbuf, n_tx);
        x[0].tx_buf = local_buf;
        x[1].rx_buf = local_buf + n_tx;

        /* do the i/o */
        status = spi_sync(spi, &message);//同步io,等待spi传输完成,然后返回用户所接收的数据和状态
        if (status == 0) {
                memcpy(rxbuf, x[1].rx_buf, n_rx);
                status = message.status;
        }

        if (x[0].tx_buf == buf)//如果使用的是预分配的缓存,释放锁好让其它人使用
                up(&lock);
        else
                kfree(local_buf);//如果使用的是临时申请的缓存,释放之

        return status;
}


/*
 * spi_sync - blocking/synchronous SPI data transfers
 * @spi: device with which data will be exchanged
 * @message: describes the data transfers
 *
 * This call may only be used from a context that may sleep.  The sleep
 * is non-interruptible, and has no timeout.  Low-overhead controller
 * drivers may DMA directly into and out of the message buffers.
 *
 * Note that the SPI device's chip select is active during the message,
 * and then is normally disabled between messages.  Drivers for some
 * frequently-used devices may want to minimize costs of selecting a chip,
 * by leaving it selected in anticipation that the next message will go
 * to the same chip.  (That may increase power usage.)
 *
 * Also, the caller is guaranteeing that the memory associated with the
 * message will not be freed before this call returns.
 *
 * The return value is a negative error code if the message could not be
 * submitted, else zero.  When the value is zero, then message->status is
 * also defined:  it's the completion code for the transfer, either zero
 * or a negative error code from the controller driver.
 */
int spi_sync(struct spi_device *spi, struct spi_message *message)
{
        DECLARE_COMPLETION_ONSTACK(done);//声明一个完成变量
        int status;

        message->complete = spi_complete;//spi传输完成后的回调函数
        message->context = &done;
        status = spi_async(spi, message);
        if (status == 0)
                wait_for_completion(&done);//等待spi传输,调用spi_complete后返回
        message->context = NULL;
        return status;
}


/*
 * spi_async -- asynchronous SPI transfer
 * @spi: device with which data will be exchanged
 * @message: describes the data transfers, including completion callback
 *
 * This call may be used in_irq and other contexts which can't sleep,
 * as well as from task contexts which can sleep.
 *
 * The completion callback is invoked in a context which can't sleep.
 * Before that invocation, the value of message->status is undefined.
 * When the callback is issued, message->status holds either zero (to
 * indicate complete success) or a negative error code.  After that
 * callback returns, the driver which issued the transfer request may
 * deallocate the associated memory; it's no longer in use by any SPI
 * core or controller driver code.
 *
 * Note that although all messages to a spi_device are handled in
 * FIFO order, messages may go to different devices in other orders.
 * Some device might be higher priority, or have various "hard" access
 * time requirements, for example.
 *
 * On detection of any fault during the transfer, processing of
 * the entire message is aborted, and the device is deselected.
 * Until returning from the associated message completion callback,
 * no other spi_message queued to that device will be processed.
 * (This rule applies equally to all the synchronous transfer calls,
 * which are wrappers around this core asynchronous primitive.)
 */
static inline int
spi_async(struct spi_device *spi, struct spi_message *message)
{
        printk("spi_async\n");

        message->spi = spi;
        return spi->master->transfer(spi, message);//调用spi_bitbang_transfer传输数据
}


/*
 * spi_bitbang_transfer - default submit to transfer queue
 */
int spi_bitbang_transfer(struct spi_device *spi, struct spi_message *m)
{
        struct spi_bitbang      *bitbang;
        unsigned long           flags;
        int                     status = 0;

        m->actual_length = 0;
        m->status = -EINPROGRESS;

        bitbang = spi_master_get_devdata(spi->master);
     /*
      * 还记得spi_alloc_master函数中调用spi_master_set_devdata把struct s3c24xx_spi结构存放起来吧?
      * 而struct spi_bitbang结构正是struct s3c24xx_spi结构所包含的第一个结构
      */
        if (bitbang->shutdown)
                return -ESHUTDOWN;

        spin_lock_irqsave(&bitbang->lock, flags);
        if (!spi->max_speed_hz)
                status = -ENETDOWN;
        else {
                list_add_tail(&m->queue, &bitbang->queue);//把message加入到bitang的等待队列中
                queue_work(bitbang->workqueue, &bitbang->work);//把bitbang-work加入bitbang->workqueue中,调度运行
        }
        spin_unlock_irqrestore(&bitbang->lock, flags);

        return status;
}

好了,稍微总结一下:
spi的读写请求通过:spi_transfer->spi_message->spi_bitbang添加都bitbang->queue中,被bitbang->work反方向提取出来执行(后面会提到)。


通过queue_work(bitbang->workqueue, &bitbang->work)把bitbang-work加入bitbang->workqueue后,在某个合适的时间,bitbang->work将被调度运行,bitbang_work函数将被调用:

/*
 * SECOND PART ... simple transfer queue runner.
 *
 * This costs a task context per controller, running the queue by
 * performing each transfer in sequence.  Smarter hardware can queue
 * several DMA transfers at once, and process several controller queues
 * in parallel; this driver doesn't match such hardware very well.
 *
 * Drivers can provide word-at-a-time i/o primitives, or provide
 * transfer-at-a-time ones to leverage dma or fifo hardware.
 */

static void bitbang_work(void *_bitbang)
{
        struct spi_bitbang      *bitbang = _bitbang;
        unsigned long           flags;

        spin_lock_irqsave(&bitbang->lock, flags);
        bitbang->busy = 1;//置忙标志
        while (!list_empty(&bitbang->queue)) {    //遍历bitbang->queue链表
                struct spi_message      *m;
                struct spi_device       *spi;
                unsigned                nsecs;
                struct spi_transfer     *t = NULL;
                unsigned                tmp;
                unsigned                cs_change;
                int                     status;
                int                     (*setup_transfer)(struct spi_device *,
                                                struct spi_transfer *);

                m = container_of(bitbang->queue.next, struct spi_message, queue);//获取spi_message结构
                list_del_init(&m->queue);//把spi_messae从queue里删除
                spin_unlock_irqrestore(&bitbang->lock, flags);

                /* FIXME this is made-up ... the correct value is known to
                 * word-at-a-time bitbang code, and presumably chipselect()
                 * should enforce these requirements too?
                 */

                nsecs = 100;

                spi = m->spi;
                tmp = 0;
                cs_change = 1;
                status = 0;
                setup_transfer = NULL;

                list_for_each_entry (t, &m->transfers, transfer_list) {//从spi_message结构的transfers链表中获取spi_transfer结构
                        if (bitbang->shutdown) {
                                status = -ESHUTDOWN;
                                break;
                           }

                        /* override or restore speed and wordsize */
                /* 本messae传输中,需要重设条件,调用setup_transfer函数 */
                        if (t->speed_hz || t->bits_per_word) {
                                setup_transfer = bitbang->setup_transfer;
                                if (!setup_transfer) {
                                        status = -ENOPROTOOPT;
                                        break;
                                }
                           }
                        if (setup_transfer) {
                                status = setup_transfer(spi, t);
                                if (status < 0)
                                        break;
                           }


                     /* set up default clock polarity, and activate chip;
                      * this implicitly updates clock and spi modes as
                      * previously recorded for this device via setup().
                      * (and also deselects any other chip that might be
                      * selected ...)
                         */
                      if (cs_change) {    //片选激活spi
                                bitbang->chipselect(spi, BITBANG_CS_ACTIVE);
                                ndelay(nsecs);
                         }
                      cs_change = t->cs_change;
                      if (!t->tx_buf && !t->rx_buf && t->len) {
                                status = -EINVAL;
                                break;
                         }

                        /* transfer data.  the lower level code handles any
                         * new dma mappings it needs. our caller always gave
                         * us dma-safe buffers.
                         */
                        if (t->len) {
                                /* REVISIT dma API still needs a designated
                                 * DMA_ADDR_INVALID; ~0 might be better.
                                 */
                                if (!m->is_dma_mapped)
                                        t->rx_dma = t->tx_dma = 0;
                                status = bitbang->txrx_bufs(spi, t);//调用s3c24xx_spi_txrx开始传输数据
                           }
                        if (status != t->len) {
                                if (status > 0)
                                        status = -EMSGSIZE;
                                break;
                           }
                        m->actual_length += status;
                        status = 0;

                        /* protocol tweaks before next transfer */
                        if (t->delay_usecs)
                                udelay(t->delay_usecs);

                        if (!cs_change)
                                continue;//不用重新片选,继续下一个message的传输
                        if (t->transfer_list.next == &m->transfers)//链表遍历完毕,退出循环
                                break;

                        /* sometimes a short mid-message deselect of the chip
                         * may be needed to terminate a mode or command
                         */
                        ndelay(nsecs);
                        bitbang->chipselect(spi, BITBANG_CS_INACTIVE);//需要重新片选的话...
                        ndelay(nsecs);
                }

                m->status = status;//所用spi_message传输完毕
                m->complete(m->context);//应答返回变量,通知等待spi传输完毕的进程(具体来说就是spi_sync函数了)

                /* restore speed and wordsize */
          /* 前面重设过条件的,在这恢复之 */
                if (setup_transfer)
                        setup_transfer(spi, NULL);

                /* normally deactivate chipselect ... unless no error and
                 * cs_change has hinted that the next message will probably
                 * be for this chip too.
                 */
                if (!(status == 0 && cs_change)) {
                        ndelay(nsecs);
                        bitbang->chipselect(spi, BITBANG_CS_INACTIVE);
                        ndelay(nsecs);
                }

                spin_lock_irqsave(&bitbang->lock, flags);//重新获取自旋锁,遍历工作者队列的下一个工作
        }
        bitbang->busy = 0;//处理完毕,清除忙标志
        spin_unlock_irqrestore(&bitbang->lock, flags);
}


static int s3c24xx_spi_txrx(struct spi_device *spi, struct spi_transfer *t)
{
        struct s3c24xx_spi *hw = to_hw(spi);

        dev_dbg(&spi->dev, "txrx: tx %p, rx %p, len %d\n",
                t->tx_buf, t->rx_buf, t->len);

        hw->tx = t->tx_buf;//发送指针
        hw->rx = t->rx_buf;//接收指针
        hw->len = t->len;//需要发送/接收的数据数目
        hw->count = 0;//存放实际spi传输的数据数目

        /* send the first byte */
        writeb(hw_txbyte(hw, 0), hw->regs + S3C2410_SPTDAT);
        wait_for_completion(&hw->done);
        /*
         * 非常有意思,这里虽然只发送第一字节,可是中断里会帮你发送完其它的字节(并接收数据),
         * 直到所有的数据发送完毕且所要接收的数据接收完毕(首要)才返回
         */

        return hw->count;
}

static irqreturn_t s3c24xx_spi_irq(int irq, void *dev, struct pt_regs *regs)
{
        struct s3c24xx_spi *hw = dev;
        unsigned int spsta = readb(hw->regs + S3C2410_SPSTA);
        unsigned int count = hw->count;

        if (hw->len){
                if (spsta & S3C2410_SPSTA_DCOL) {
                        dev_dbg(hw->dev, "data-collision\n");//检测冲突
                        complete(&hw->done);
                        goto irq_done;
                }

                if (!(spsta & S3C2410_SPSTA_READY)) {
                        dev_dbg(hw->dev, "spi not ready for tx?\n");//设备忙
                        complete(&hw->done);
                        goto irq_done;
                }

                hw->count++;

                if (hw->rx)
                        hw->rx[count] = readb(hw->regs + S3C2410_SPRDAT);//接收数据

                count++;

                if (count < hw->len)
                        writeb(hw_txbyte(hw, count), hw->regs + S3C2410_SPTDAT);//发送其它数据(或空数据0xFF)
                else
                        complete(&hw->done);//发送接收完毕,通知s3c24xx_spi_txrx函数
        }

 irq_done:
        return IRQ_HANDLED;
}

static inline unsigned int hw_txbyte(struct s3c24xx_spi *hw, int count)
{
        return hw->tx ? hw->tx[count] : 0xff;
        //如果还有数据没接收完且要发送的数据经已发送完毕,发送空数据0xFF
}


注:
    这里要注意的是:在spi提供的write_then_read函数中,写和读数据是分开两个阶段来进行的(写数据的时候不读数据;读数据的时候发送空数据0xff)。


总结:
    简单的spi子系统大致就是这样,相对比较简单易懂,具体的应用可以参考一下代spi接口的触摸屏控制芯片驱动:
driver/input/touchscreen/ads7846.c
不过看明白它需要多花些时间了,因为毕竟这个驱动不仅和spi子系统打交道而且还和input子系统打交道,可不是那么容易应付的哦^_^

后记:
    1、关于spi的使用,可以参考一下这个帖子:、
http://www.linuxforum.net/forum/gshowflat.php?Cat=&Board=embedded&Number=646262&page=1&view=collapsed&sb=5&o=all&fpart=

    具体是另外编写一个设备驱动,还需要在设备配置文件(旧版本则为dev.c文件)中添加一些结构,来添加一个设备(使用spi总线驱动),而我们另外编写的设备驱动则与这个新添加的设备通过名字来关联(名字很难重要),那我们就可以通过这个另外编写的设备驱动来操作spi总线了,详细使用情况我在这个贴上有说,就不再重复提了:
http://www.linuxforum.net/forum/showflat.php?Cat=&Board=embedded&Number=665008&page=7&view=collapsed&sb=5&o=0&fpart=

    2、其实内核已经提供了这样的一个参考程序了,就是spidev,所以不用另外编写设备驱动了,选上spidev直接使用就可以了^_^

posted @ 2007-09-25 08:42 lfc 阅读(10036) | 评论 (6)编辑 收藏
2.6.18内核下已经添加了完整的spi子系统了,参考mtd的分析,将从下到上层,再从上到下层的对其进行分析。

以下先从下到上的进行分析:
driver/spi下有两个底层相关的spi驱动程序:
spi_s3c24xx.c和spi_s3c24xx_gpio.c
其中spi_s3c24xx.c是基于s3c24xx下相应的spi接口的驱动程序,spi_s3c24xx_gpio.c允许用户指定3个gpio口,分别充当spi_clk、spi_mosi和spi_miso接口,模拟标准的spi总线。
s3c2410自带了两个spi接口(spi0和spi1),在此我只研究基于s3c2410下spi接口的驱动程序spi_s3c24xx.c。
首先从spi驱动的检测函数进行分析:
static int s3c24xx_spi_probe(struct platform_device *pdev)
{
        struct s3c24xx_spi *hw;
        struct spi_master *master;
        struct spi_board_info *bi;
        struct resource *res;
        int err = 0;
        int i;

    /* pi_alloc_master函数申请了struct spi_master+struct s3c24xx_spi大小的数据,
         * spi_master_get_devdata和spi_master_get分别取出struct s3c24xx_spi和struct spi_master结构指针
          */
        master = spi_alloc_master(&pdev->dev, sizeof(struct s3c24xx_spi));
        if (master == NULL) {
                dev_err(&pdev->dev, "No memory for spi_master\n");
                err = -ENOMEM;
                goto err_nomem;
         }

     /* 填充struct spi_master结构 */
        hw = spi_master_get_devdata(master);
        memset(hw, 0, sizeof(struct s3c24xx_spi));

        hw->master = spi_master_get(master);
        hw->pdata = pdev->dev.platform_data;
        hw->dev = &pdev->dev;

        if (hw->pdata == NULL) {
                dev_err(&pdev->dev, "No platform data supplied\n");
                err = -ENOENT;
                goto err_no_pdata;
        }

        platform_set_drvdata(pdev, hw);//dev_set_drvdata(&pdev->dev, hw)
        init_completion(&hw->done);

        /* setup the state for the bitbang driver */
   
     /* 填充hw->bitbang结构(hw->bitbang结构充当一个中间层,相当与input system的input_handle struct) */
        hw->bitbang.master         = hw->master;
        hw->bitbang.setup_transfer = s3c24xx_spi_setupxfer;
        hw->bitbang.chipselect     = s3c24xx_spi_chipsel;
        hw->bitbang.txrx_bufs      = s3c24xx_spi_txrx;
        hw->bitbang.master->setup  = s3c24xx_spi_setup;

        dev_dbg(hw->dev, "bitbang at %p\n", &hw->bitbang);

        /* find and map our resources */
         /* 申请spi所用到的资源:io、irq、时钟等 */
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        if (res == NULL) {
                dev_err(&pdev->dev, "Cannot get IORESOURCE_MEM\n");
                err = -ENOENT;
                goto err_no_iores;
        }

        hw->ioarea = request_mem_region(res->start, (res->end - res->start)+1,
                                        pdev->name);

        if (hw->ioarea == NULL) {
                dev_err(&pdev->dev, "Cannot reserve region\n");
                err = -ENXIO;
                goto err_no_iores;
        }

        hw->regs = ioremap(res->start, (res->end - res->start)+1);
        if (hw->regs == NULL) {
                dev_err(&pdev->dev, "Cannot map IO\n");
                err = -ENXIO;
                goto err_no_iomap;
        }

        hw->irq = platform_get_irq(pdev, 0);
        if (hw->irq < 0) {
                dev_err(&pdev->dev, "No IRQ specified\n");
                err = -ENOENT;
                goto err_no_irq;
        }

        err = request_irq(hw->irq, s3c24xx_spi_irq, 0, pdev->name, hw);
        if (err) {
                dev_err(&pdev->dev, "Cannot claim IRQ\n");
                goto err_no_irq;
        }

        hw->clk = clk_get(&pdev->dev, "spi");
        if (IS_ERR(hw->clk)) {
                dev_err(&pdev->dev, "No clock for device\n");
                err = PTR_ERR(hw->clk);
                goto err_no_clk;
        }

        /* for the moment, permanently enable the clock */

        clk_enable(hw->clk);

        /* program defaults into the registers */
         /* 初始化spi相关的寄存器 */
        writeb(0xff, hw->regs + S3C2410_SPPRE);
        writeb(SPPIN_DEFAULT, hw->regs + S3C2410_SPPIN);
        writeb(SPCON_DEFAULT, hw->regs + S3C2410_SPCON);
        /* add by lfc */
        s3c2410_gpio_setpin(S3C2410_GPE13, 0);
        s3c2410_gpio_setpin(S3C2410_GPE12, 0);
        s3c2410_gpio_cfgpin(S3C2410_GPE13, S3C2410_GPE13_SPICLK0);
        s3c2410_gpio_cfgpin(S3C2410_GPE12, S3C2410_GPE12_SPIMOSI0);
        s3c2410_gpio_cfgpin(S3C2410_GPE11, S3C2410_GPE11_SPIMISO0);
        /* end add */

        /* setup any gpio we can */
     /* 片选 */
        if (!hw->pdata->set_cs) {
                s3c2410_gpio_setpin(hw->pdata->pin_cs, 1);
                s3c2410_gpio_cfgpin(hw->pdata->pin_cs, S3C2410_GPIO_OUTPUT);
        }

        /* register our spi controller */
         /* 最终通过调用spi_register_master来注册spi控制器(驱动) */
        err = spi_bitbang_start(&hw->bitbang);
        if (err) {
                dev_err(&pdev->dev, "Failed to register SPI master\n");
                goto err_register;
        }

        dev_dbg(hw->dev, "shutdown=%d\n", hw->bitbang.shutdown);

        /* register all the devices associated */
         /* 注册所用使用本spi驱动的设备 */

        bi = &hw->pdata->board_info[0];
        for (i = 0; i < hw->pdata->board_size; i++, bi++) {
                dev_info(hw->dev, "registering %s\n", bi->modalias);

                bi->controller_data = hw;
                spi_new_device(master, bi);
        }

        return 0;

 err_register:
        clk_disable(hw->clk);
        clk_put(hw->clk);

 err_no_clk:
        free_irq(hw->irq, hw);

 err_no_irq:
        iounmap(hw->regs);

 err_no_iomap:
        release_resource(hw->ioarea);
        kfree(hw->ioarea);

 err_no_iores:
 err_no_pdata:
        spi_master_put(hw->master);;

 err_nomem:
        return err;
}

/*
 * spi_alloc_master - allocate SPI master controller
 * @dev: the controller, possibly using the platform_bus
 * @size: how much driver-private data to preallocate; the pointer to this
 *      memory is in the class_data field of the returned class_device,
 *      accessible with spi_master_get_devdata().
 *
 * This call is used only by SPI master controller drivers, which are the
 * only ones directly touching chip registers.  It's how they allocate
 * an spi_master structure, prior to calling spi_register_master().
 *
 * This must be called from context that can sleep.  It returns the SPI
 * master structure on success, else NULL.
 *
 * The caller is responsible for assigning the bus number and initializing
 * the master's methods before calling spi_register_master(); and (after errors
 * adding the device) calling spi_master_put() to prevent a memory leak.
 */
/*注释已经写得很清楚了,本函数旨在分配spi_master struct
 *其中,device为主控制设备,size为需要预分配的设备私有数据大小
 *该函数被spi主控制器驱动所调用,用于在调用spi_register_master注册主控制器前
 *分配spi_master struct,分配bus number和初始化主控制器的操作方法
 *注意在分配spi_master struct的时候多分配了大小为size的设备私有数据
 *并通过spi_master_set_devdata函数把其放到class_data field里,以后可以通过spi_master_get_devdata来访问
 */
struct spi_master * __init_or_module
spi_alloc_master(struct device *dev, unsigned size)
{
        struct spi_master       *master;

        if (!dev)
                return NULL;

        master = kzalloc(size + sizeof *master, SLAB_KERNEL);
        if (!master)
                return NULL;

        class_device_initialize(&master->cdev);
        master->cdev.class = &spi_master_class;
        master->cdev.dev = get_device(dev);
        spi_master_set_devdata(master, &master[1]);

        return master;
}


/*
 * spi_bitbang_start - start up a polled/bitbanging SPI master driver
 * @bitbang: driver handle
 *
 * Caller should have zero-initialized all parts of the structure, and then
 * provided callbacks for chip selection and I/O loops.  If the master has
 * a transfer method, its final step should call spi_bitbang_transfer; or,
 * that's the default if the transfer routine is not initialized.  It should
 * also set up the bus number and number of chipselects.
 *
 * For i/o loops, provide callbacks either per-word (for bitbanging, or for
 * hardware that basically exposes a shift register) or per-spi_transfer
 * (which takes better advantage of hardware like fifos or DMA engines).
 *
 * Drivers using per-word I/O loops should use (or call) spi_bitbang_setup and
 * spi_bitbang_cleanup to handle those spi master methods.  Those methods are
 * the defaults if the bitbang->txrx_bufs routine isn't initialized.
 *
 * This routine registers the spi_master, which will process requests in a
 * dedicated task, keeping IRQs unblocked most of the time.  To stop
 * processing those requests, call spi_bitbang_stop().
 */
int spi_bitbang_start(struct spi_bitbang *bitbang)
{
        int     status;

        if (!bitbang->master || !bitbang->chipselect)
                return -EINVAL;
     /*bitbang_work
       * 初始化a work,后面再create_singlethread_workqueue,
      * 等到有数据要传输的时候,在spi_bitbang_transfer函数中通过调用queue_work(bitbang->workqueue, &bitbang->work)
      * 把work扔进workqueue中调度运行
      * 这是内核的一贯做法,在mmc/sd驱动中也是这样处理的^_^
      */
        INIT_WORK(&bitbang->work, bitbang_work, bitbang);

     /* 初始化自旋锁和链表头,以后用到 */
        spin_lock_init(&bitbang->lock);
     spi_new_device   INIT_LIST_HEAD(&bitbang->queue);

        if (!bitbang->master->transfer)
                bitbang->master->transfer = spi_bitbang_transfer;//spi数据的传输就是通过调用这个方法来实现的
     /* spi_s3c24xx.c驱动中有相应的txrx_bufs处理方法,在bitbang_work中被调用 */
        if (!bitbang->txrx_bufs) {
                bitbang->use_dma = 0;
                bitbang->txrx_bufs = spi_bitbang_bufs;
                if (!bitbang->master->setup) {
                        if (!bitbang->setup_transfer)
                                bitbang->setup_transfer =
                                         spi_bitbang_setup_transfer;
                        bitbang->master->setup = spi_bitbang_setup;
                        bitbang->master->cleanup = spi_bitbang_cleanup;
                }
     /* spi_s3c24xx.c驱动中有相应的setup处理方法,在稍后的spi_new_device中被调用 */
        } else if (!bitbang->master->setup)
                return -EINVAL;

        /* this task is the only thing to touch the SPI bits */
        bitbang->busy = 0;
     /* 创建工作者进程 */
        bitbang->workqueue = create_singlethread_workqueue(
                        bitbang->master->cdev.dev->bus_id);
        if (bitbang->workqueue == NULL) {
                status = -EBUSY;
                goto err1;
        }

        /* driver may get busy before register() returns, especially
         * if someone registered boardinfo for devices
         */
        status = spi_register_master(bitbang->master);
        if (status < 0)
                goto err2;

        return status;

err2:
        destroy_workqueue(bitbang->workqueue);
err1:
        return status;
}


/**
 * spi_register_master - register SPI master controller
 * @master: initialized master, originally from spi_alloc_master()
 *
 * SPI master controllers connect to their drivers using some non-SPI bus,
 * such as the platform bus.  The final stage of probe() in that code
 * includes calling spi_register_master() to hook up to this SPI bus glue.
 *
 * SPI controllers use board specific (often SOC specific) bus numbers,
 * and board-specific addressing for SPI devices combines those numbers
 * with chip select numbers.  Since SPI does not directly support dynamic
 * device identification, boards need configuration tables telling which
 * chip is at which address.
 *
 * This must be called from context that can sleep.  It returns zero on
 * success, else a negative error code (dropping the master's refcount).
 * After a successful return, the caller is responsible for calling
 * spi_unregister_master().
 */
int __init_or_module
spi_register_master(struct spi_master *master)
{
        static atomic_t         dyn_bus_id = ATOMIC_INIT((1<<16) - 1);
        struct device           *dev = master->cdev.dev;
        int                     status = -ENODEV;
        int                     dynamic = 0;

        if (!dev)
                return -ENODEV;

        /* convention:  dynamically assigned bus IDs count down from the max */
        if (master->bus_num < 0) {
                master->bus_num = atomic_dec_return(&dyn_bus_id);
                dynamic = 1;
        }

        /* register the device, then userspace will see it.
         * registration fails if the bus ID is in use.
         */
        snprintf(master->cdev.class_id, sizeof master->cdev.class_id,
                "spi%u", master->bus_num);
        status = class_device_add(&master->cdev);//注册设备
        if (status < 0)
                goto done;
        dev_dbg(dev, "registered master %s%s\n", master->cdev.class_id,
                        dynamic ? " (dynamic)" : "");

        /* populate children from any spi device tables */
        scan_boardinfo(master);
        status = 0;
done:
        return status;
}


/* FIXME someone should add support for a __setup("spi", ...) that
 * creates board info from kernel command lines
 */

/*
 * scan board_list for spi_board_info which is registered by spi_register_board_info
 * 很可惜,s3c24xx的spi驱动中不支持spi_register_board_info这种标准方式注册方式,而是直接调用spi_new_device内部函数
 */
后记:
    如果像atmel那样,初始化的时候调用spi_register_board_info函数注册spi设备信息,那么
调用spi_register_master注册spi控制器的时候,就可以通过调用scan_boardinfo函数,完成spi设备的注册了^_^。

static void __init_or_module
scan_boardinfo(struct spi_master *master)
{
        struct boardinfo        *bi;
        struct device           *dev = master->cdev.dev;

        down(&board_lock);
        list_for_each_entry(bi, &board_list, list) {
                struct spi_board_info   *chip = bi->board_info;
                unsigned                n;

                for (n = bi->n_board_info; n > 0; n--, chip++) {
                        if (chip->bus_num != master->bus_num)
                                continue;
                        /* some controllers only have one chip, so they
                         * might not use chipselects.  otherwise, the
                         * chipselects are numbered 0..max.
                         */
                        if (chip->chip_select >= master->num_chipselect
                                        && master->num_chipselect) {
                                dev_dbg(dev, "cs%d > max %d\n",
                                        chip->chip_select,
                                        master->num_chipselect);
                                continue;
                        }
                        (void) spi_new_device(master, chip);
                }
        }
        up(&board_lock);
}


/*
 * Board-specific early init code calls this (probably during arch_initcall)
 * with segments of the SPI device table.  Any device nodes are created later,
 * after the relevant parent SPI controller (bus_num) is defined.  We keep
 * this table of devices forever, so that reloading a controller driver will
 * not make Linux forget about these hard-wired devices.
 *
 * Other code can also call this, e.g. a particular add-on board might provide
 * SPI devices through its expansion connector, so code initializing that board
 * would naturally declare its SPI devices.
 *
 * The board info passed can safely be __initdata ... but be careful of
 * any embedded pointers (platform_data, etc), they're copied as-is.
 */
int __init
spi_register_board_info(struct spi_board_info const *info, unsigned n)
{
        struct boardinfo        *bi;

        bi = kmalloc(sizeof(*bi) + n * sizeof *info, GFP_KERNEL);
        if (!bi)
                return -ENOMEM;
        bi->n_board_info = n;
        memcpy(bi->board_info, info, n * sizeof *info);

        down(&board_lock);
        list_add_tail(&bi->list, &board_list);
        up(&board_lock);
        return 0;
}


/* On typical mainboards, this is purely internal; and it's not needed
 * after board init creates the hard-wired devices.  Some development
 * platforms may not be able to use spi_register_board_info though, and
 * this is exported so that for example a USB or parport based adapter
 * driver could add devices (which it would learn about out-of-band).
 */
struct spi_device *__init_or_module
spi_new_device(struct spi_master *master, struct spi_board_info *chip)
{
        struct spi_device       *proxy;//这个结构很重要,以后就是通过这个结构来操作实际的spi设备的
        struct device           *dev = master->cdev.dev;
        int                     status;

        /* NOTE:  caller did any chip->bus_num checks necessary */

        if (!spi_master_get(master))
                return NULL;

        proxy = kzalloc(sizeof *proxy, GFP_KERNEL);
        if (!proxy) {
                dev_err(dev, "can't alloc dev for cs%d\n",
                        chip->chip_select);
                goto fail;
         }
     /* 初始化spi_device 结构各成员 */
        proxy->master = master;
        proxy->chip_select = chip->chip_select;
        proxy->max_speed_hz = chip->max_speed_hz;
        proxy->mode = chip->mode;
        proxy->irq = chip->irq;
        proxy->modalias = chip->modalias;

        snprintf(proxy->dev.bus_id, sizeof proxy->dev.bus_id,
                        "%s.%u", master->cdev.class_id,
                        chip->chip_select);
        proxy->dev.parent = dev;
        proxy->dev.bus = &spi_bus_type;
        proxy->dev.platform_data = (void *) chip->platform_data;
        proxy->controller_data = chip->controller_data;
        proxy->controller_state = NULL;
        proxy->dev.release = spidev_release;

        /* drivers may modify this default i/o setup */
     /* 调用master->setup(即s3c24xx_spi_setup)函数初始化spi设备 */
        status = master->setup(proxy);
        if (status < 0) {
                dev_dbg(dev, "can't %s %s, status %d\n",
                                "setup", proxy->dev.bus_id, status);
                goto fail;
        }

        /* driver core catches callers that misbehave by defining
         * devices that already exist.
         */
        status = device_register(&proxy->dev);//真正注册原始设备
        if (status < 0) {
                dev_dbg(dev, "can't %s %s, status %d\n",
                                "add", proxy->dev.bus_id, status);
                goto fail;
        }
        dev_dbg(dev, "registered child %s\n", proxy->dev.bus_id);
        return proxy;

fail:
        spi_master_put(master);
        kfree(proxy);
        return NULL;
}

好了,至此spi主控制器(驱动)和板上spi设备注册完毕,以后要使用spi来传输数据的话,只要先获得spi设备结构,然后就可以利用它来和spi驱动打交道了(就好像你要操作一个文件,先要获取文件句柄一样,明白吧^_^)

static int s3c24xx_spi_setup(struct spi_device *spi)
{
        int ret;
     /* 进行一些检查性操作 */
        if (!spi->bits_per_word)
                spi->bits_per_word = 8;

        if ((spi->mode & SPI_LSB_FIRST) != 0)
                return -EINVAL;

        ret = s3c24xx_spi_setupxfer(spi, NULL);
        if (ret < 0) {
                dev_err(&spi->dev, "setupxfer returned %d\n", ret);
                return ret;
        }

        dev_dbg(&spi->dev, "%s: mode %d, %u bpw, %d hz\n",
                __FUNCTION__, spi->mode, spi->bits_per_word,
                spi->max_speed_hz);

        return 0;
}


static int s3c24xx_spi_setupxfer(struct spi_device *spi,
                                 struct spi_transfer *t)
{
        struct s3c24xx_spi *hw = to_hw(spi);
        unsigned int bpw;
        unsigned int hz;
        unsigned int div;

        bpw = t ? t->bits_per_word : spi->bits_per_word;
        hz  = t ? t->speed_hz : spi->max_speed_hz;

        if (bpw != 8) {
                dev_err(&spi->dev, "invalid bits-per-word (%d)\n", bpw);
                return -EINVAL;
        }

        div = clk_get_rate(hw->clk) / hz;

        /* is clk = pclk / (2 * (pre+1)), or is it
         *    clk = (pclk * 2) / ( pre + 1) */

        div = (div / 2) - 1;//求出预分频值

        if (div < 0)
                div = 1;

        if (div > 255)
                div = 255;

        dev_dbg(&spi->dev, "setting pre-scaler to %d (hz %d)\n", div, hz);
        writeb(div, hw->regs + S3C2410_SPPRE);//设置预分频值

        spin_lock(&hw->bitbang.lock);
        if (!hw->bitbang.busy) {
                hw->bitbang.chipselect(spi, BITBANG_CS_INACTIVE);//修改时钟,先禁用spi
                /* need to ndelay for 0.5 clocktick ? */
        }
        spin_unlock(&hw->bitbang.lock);

        return 0;
}


static void s3c24xx_spi_chipsel(struct spi_device *spi, int value)
{
        struct s3c24xx_spi *hw = to_hw(spi);
        unsigned int cspol = spi->mode & SPI_CS_HIGH ? 1 : 0;
        unsigned int spcon;

        switch (value) {
        case BITBANG_CS_INACTIVE:
     /* 禁用spi(禁用片选) */
                if (hw->pdata->set_cs)
                        hw->pdata->set_cs(hw->pdata, value, cspol);
                else
                        s3c2410_gpio_setpin(hw->pdata->pin_cs, cspol ^ 1);
                break;

        case BITBANG_CS_ACTIVE:
    /*
     * 启用spi:根据需要设置寄存器并启用使能片选
     * (如果spi_board_info中没有设置相应的mode选项的话,那就只能使用默认值SPPIN_DEFAULT和SPCON_DEFAULT了)
     */
                spcon = readb(hw->regs + S3C2410_SPCON);

                if (spi->mode & SPI_CPHA)
                        spcon |= S3C2410_SPCON_CPHA_FMTB;
                else
                        spcon &= ~S3C2410_SPCON_CPHA_FMTB;

                if (spi->mode & SPI_CPOL)
                        spcon |= S3C2410_SPCON_CPOL_HIGH;
                else
                        spcon &= ~S3C2410_SPCON_CPOL_HIGH;

                spcon |= S3C2410_SPCON_ENSCK;

                /* write new configration */

                writeb(spcon, hw->regs + S3C2410_SPCON);

                if (hw->pdata->set_cs)
                        hw->pdata->set_cs(hw->pdata, value, cspol);
                else
                        s3c2410_gpio_setpin(hw->pdata->pin_cs, cspol);

                break;

        }
}



















posted @ 2007-09-24 08:45 lfc 阅读(6172) | 评论 (5)编辑 收藏
仅列出标题
共12页: First 4 5 6 7 8 9 10 11 12