免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1352 | 回复: 0
打印 上一主题 下一主题

Linux设备驱动程序学习(9)-与硬件通信 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-03-28 10:37 |只看该作者 |倒序浏览

               
[color="#0000ff"]

[color="#0000ff"]在学习有关I/O总线的内容时,最好先看看相关的知识:从PC总线到ARM的内部总线[color="#000000"]

[color="#0000ff"]I/O 端口和 I/O 内存
[color="#000000"]每种外设都是通过读写寄存器来进行控制。
[color="#000000"]在硬件层,内存区和 I/O 区域没有概念上的区别: 它们都是通过向在地址总线和控制总线发出电平信号来进行访问,再通过数据总线读写数据。
[color="#000000"]因为外设要与I\O总线匹配,而大部分流行的 I/O 总线是基于个人计算机模型(主要是 x86 家族:它为读和写 I/O 端口提供了独立的线路和特殊的 CPU 指令),所以即便那些没有单独I/O 端口地址空间的处理器,在访问外设时也要模拟成读写I\O端口。这一功能通常由外围芯片组(PC 中的南北桥)或 [color="#0000ff"]CPU 中的附加电路实现(嵌入式中的方法) 。
Linux 在所有的计算机平台上实现了 I/O 端口。但不是所有的设备都将寄存器映射到
I/O 端口。虽然ISA设备普遍使用 I/O 端口,但大部分 PCI 设备则把寄存器映射到某个内存地址区,这种 I/O
内存方法通常是首选的。因为它无需使用特殊的处理器指令,CPU
核访问内存更有效率,且编译器在访问内存时在寄存器分配和寻址模式的选择上有更多自由。
[color="#0000ff"]I/O 寄存器和常规内存
在进入这部分学习的时候,首先要理解一个概念:side
effect,书中译为边际效应,第二版译为副作用。我觉得不管它是怎么被翻译的,都不可能精准表达原作者的意思,所以我个人认为记住side
effect就好。下面来讲讲side effect的含义。我先贴出两个网上已有的两种说法([color="#ff0000"]在这里谢谢两位高人的分享):
[color="#0000ff"]第一种说法:
3. side effect(译为边际效应或副作用):是指读取某个地址时可能导致该地址内容发生变化,比如,有些设备的中断状态寄存器只要一读取,便自动清零。I/O寄存器的操作具有side effect,因此,不能对其操作不能使用cpu缓存。
原文网址:
http://qinbh.blog.sohu.com/62733495.html
[color="#0000ff"]第二种说法:
说一下我的理解:I/O端口与实际外部设备相关联,通过访问I/O端口控制外部设备,“边际效应”是指控制设备(读取或写入)生效,访问I/O口的
主要目的就是边际效应,不像访问普通的内存,只是在一个位置存储或读取一个数值,没有别的含义了。我是基于ARM平台理解的,在《linux设备驱动程
序》第二版中的说法是“副作用”,不是“边际效应”。
原文网址:
http://linux.chinaunix.net/bbs/viewthread.php?tid=890636&page=1#pid6312646

结合以上两种说法和自己看《Linux设备驱动程序(第3版)》的理解,我个人认为可以这样解释:

side effect
是指:访问I/O寄存器时,不仅仅会像访问普通内存一样影响存储单元的值,更重要的是它可能改变CPU的I/O端口电平、输出时序或CPU对I/O端口电
平的反应等等,从而实现CPU的控制功能。CPU在电路中的意义就是实现其side effect 。


I/O 寄存器和 RAM 的主要不同就是 I/O 寄存器操作有side effect, 而内存操作没有。
因为存储单元的访问速度对 CPU 性能至关重要,编译器会对源代码进行优化,主要是: 使用高速缓存保存数值 和 重新编排读/写指令顺序。但对I/O 寄存器操作来说,这些优化可能造成致命错误。因此,[color="#0000ff"]驱动程序必须确保在操作I/O 寄存器时,不使用高速缓存,且不能重新编排读/写指令顺序。
[color="#0000ff"]解决方法:
[color="#0000ff"]硬件缓存问题:只要把底层硬件配置(自动地或者通过 Linux 初始化代码)成当访问 I/O 区域时(不管内存还是端口)禁止硬件缓存即可。
[color="#0000ff"]硬件指令重新排序问题:在硬件(或其他处理器)必须以一个特定顺序执行的操作之间设置内存屏障(memory barrier)。
[color="#0000ff"]Linux 提供以下宏来解决所有可能的排序问题:
#include linux/kernel.h>
void barrier(void) /*告知编译器插入一个内存屏障但是对硬件没有影响。编译后的代码会将当前CPU 寄存器中所有修改过的数值保存到内存中, 并当需要时重新读取它[color="#ff9900"]们。可阻止在屏障前后的编译器优化,但硬件能完成自己的重新排序。其实linux/kernel.h> 中并没有这个函数,因为它是在[color="#000000"]kernel.h包含的头文件[color="#000000"]compiler.h中定义的*/
#include linux/compiler.h>
# define barrier() __memory_barrier()
#include asm/system.h>
void rmb(void); /*保证任何出现于屏障前的读在执行任何后续的读之前完成*/
void wmb(void); /*保证任何出现于屏障前的写在执行任何后续的写之前完成*/
void mb(void); /*保证任何出现于屏障前的读写操作在执行任何后续的读写操作之前完成*/
void read_barrier_depends(void); /*
一种特殊的、弱些的读屏障形式。rmb 阻止屏障前后的所有读指令的重新排序,read_barrier_depends
只阻止依赖于其他读指令返回的数据的读指令的重新排序。区别微小, 且不在所有体系中存在。除非你确切地理解它们的差别,
并确信完整的读屏障会增加系统开销,否则应当始终使用 rmb。*/
/*以上指令是barrier的超集*/
void smp_rmb(void);
void smp_read_barrier_depends(void);
void smp_wmb(void);
void smp_mb(void);
/*仅当内核为 SMP 系统编译时插入硬件屏障; 否则, 它们都扩展为一个简单的屏障调用。*/
典型的应用:
writel(dev->registers.addr, io_destination_address);
writel(dev->registers.size, io_size);
writel(dev->registers.operation, DEV_READ);
wmb();/*类似一条分界线,上面的写操作必然会在下面的写操作前完成,但是上面的三个写操作的排序无法保证*/
writel(dev->registers.control, DEV_GO);
内存屏障影响性能,所以应当只在确实需要它们的地方使用。不同的类型对性能的影响也不同,因此要尽可能地使用需要的特定类型。值得注意的是大部分处理同步的内核原语,例如自旋锁和atomic_t,也可作为内存屏障使用。
某些体系允许赋值和内存屏障组合,以提高效率。它们定义如下:
#define set_mb(var, value) do {var = value; mb();} while 0
/*以下宏定义在ARM体系中不存在*/
#define set_wmb(var, value) do {var = value; wmb();} while 0
#define set_rmb(var, value) do {var = value; rmb();} while 0
使用do...while 结构来构造宏是标准 C 的惯用方法,它保证了扩展后的宏可在所有上下文环境中被作为一个正常的 C 语句执行。
[color="#0000ff"]使用 I/O 端口
I/O 端口是驱动用来和许多设备之间的通讯方式。
[color="#0000ff"]I/O 端口分配
[color="#0000ff"]在尚未取得端口的独占访问前,不应对端口进行操作。内核提供了一个注册用的接口,允许驱动程序声明它需要的端口:
#include linux/ioport.h>
struct resource *request_region(unsigned long first, unsigned long n, const char *name);/*告诉内核:要使用从 first 开始的 n 个端口,name 参数为设备名。若分配成功返回非 NULL,否则将无法使用需要的端口。*/
/*所有的的端口分配显示在 /proc/ioports 中。若不能分配到需要的端口,则可以到这里看看谁先用了。*/
/*当用完 I/O 端口集(可能在模块卸载时), 应当将它们返回给系统*/
void release_region(unsigned long start, unsigned long n);
int check_region(unsigned long first, unsigned long n);
/*检查一个给定的 I/O 端口集是否可用,若不可用, 返回值是一个负错误码。不推荐使用*/
[color="#0000ff"]操作 I/O 端口
在驱动程序注册I/O 端口后,就可以读/写这些端口。大部分硬件会把8、16和32位端口区分开,不能像访问系统内存那样混淆使用。驱动必须调用不同的函数来存取不同大小的端口。
只支持内存映射的 I/O 寄存器的计算机体系通过重新映射I/O端口到内存地址来伪装端口I/O。为了提高移植性,内核向驱动隐藏了这些细节。Linux 内核头文件(体系依赖的头文件 [color="#0000ff"] ) 定义了下列内联函数(有的体系是宏,有的不存在)来访问 I/O 端口:
unsigned inb(unsigned port);
void outb(unsigned char byte, unsigned port);
/*读/写字节端口( 8 位宽 )。port 参数某些平台定义为 unsigned long ,有些为 unsigned short 。 inb 的返回类型也体系而不同。*/
unsigned inw(unsigned port);
void outw(unsigned short word, unsigned port);
/*访问 16位 端口( 一个字宽 )*/
unsigned inl(unsigned port);
void outl(unsigned longword, unsigned port);
/*访问 32位 端口。 longword 声明有的平台为 unsigned long ,有的为 unsigned int。*/
[color="#0000ff"]在用户空间访问 I/O 端口
以上函数主要提供给设备驱动使用,但它们也可在用户空间使用,至少在 PC上可以。 GNU C 库在  中定义了它们。如果在用户空间代码中使用必须满足以下条件:
(1)程序必须使用 -O 选项编译来强制扩展内联函数。
(2)必须用ioperm 和 iopl 系统调用[color="#660099"](#include ) 来获得对端口 I/O 操作的权限。ioperm 为获取单独端口操作权限,而 iopl 为整个 I/O 空间的操作权限。[color="#660099"] (x86 特有的)
(3)程序以 root 来调用 ioperm 和 iopl,或是其父进程必须以 root 获得端口操作权限。[color="#660099"](x86 特有的)
[color="#0000ff"]若平台没有 ioperm 和 iopl 系统调用,用户空间可以仍然通过使用 /dev/prot 设备文件访问 I/O 端口。注意:这个文件的定义是体系相关的,并且I/O 端口必须先被注册。
[color="#0000ff"]串操作
除了一次传输一个数据的I/O操作,一些处理器实现了一次传输一个数据序列的特殊指令,序列中的数据单位可以是字节、字或双字,这是所谓的串操作指
令。它们完成任务比一个 C 语言循环更快。下列宏定义实现了串I/O,它们有的通过单个机器指令实现;但如果目标处理器没有进行串 I/O
的指令,则通过执行一个紧凑的循环实现。 有的体系的原型如下:
void insb(unsigned port, void *addr, unsigned long count);
void outsb(unsigned port, void *addr, unsigned long count);
void insw(unsigned port, void *addr, unsigned long count);
void outsw(unsigned port, void *addr, unsigned long count);
void insl(unsigned port, void *addr, unsigned long count);
void outsl(unsigned port, void *addr, unsigned long count);
使用时注意: 它们直接将字节流从端口中读取或写入。当端口和主机系统有不同的字节序时,会导致不可预期的结果。 使用 inw 读取端口应在必要时自行转换字节序,以匹配主机字节序。
[color="#0000ff"]暂停式 I/O
[color="#000000"]为了匹配低速外设的速度,有时若 I/O 指令后面还紧跟着另一个类似的I/O指令,就必须在 I/O 指令后面插入一个小延时。在
这种情况下,可以使用暂停式的I/O函数代替通常的I/O函数,它们的名字以 _p 结尾,如 inb_p、outb_p等等。
这些函数定义被大部分体系支持,尽管它们常常被扩展为与非暂停式I/O
同样的代码。因为如果体系使用一个合理的现代外设总线,就没有必要额外暂停。细节可参考平台的 asm 子目录的 io.h
文件。以下是include\asm-arm\io.h中的宏定义:
#define outb_p(val,port)    outb((val),(port))
#define outw_p(val,port)    outw((val),(port))
#define outl_p(val,port)    outl((val),(port))
#define inb_p(port)        inb((port))
#define inw_p(port)        inw((port))
#define inl_p(port)        inl((port))
#define outsb_p(port,from,len)    outsb(port,from,len)
#define outsw_p(port,from,len)    outsw(port,from,len)
#define outsl_p(port,from,len)    outsl(port,from,len)
#define insb_p(port,to,len)    insb(port,to,len)
#define insw_p(port,to,len)    insw(port,to,len)
#define insl_p(port,to,len)    insl(port,to,len)
由此可见,由于ARM使用内部总线,就没有必要额外暂停,所以暂停式的I/O函数被扩展为与非暂停式I/O 同样的代码。
[color="#0000ff"]平台相关性
由于自身的特性,I/O 指令与处理器密切相关的,非常难以隐藏系统间的不同。所以大部分的关于端口 I/O 的源码是平台依赖的。以下是x86和ARM所使用函数的总结:
IA-32 (x86)
x86_64
这个体系支持所有的以上描述的函数,端口号是 unsigned short 类型。
[color="#0000ff"]ARM
端口映射到内存,支持所有函数。串操作 用C语言实现。端口是 unsigned int 类型。
[color="#0000ff"]使用 I/O 内存
除了 x86上普遍使用的I/O
端口外,和设备通讯另一种主要机制是通过使用映射到内存的寄存器或设备内存,统称为 I/O 内存。因为寄存器和内存之间的区别对软件是透明的。I/O
内存仅仅是类似 RAM 的一个区域,处理器通过总线访问这个区域,以实现设备的访问。
根据平台和总线的不同,I/O
内存可以就是否通过页表访问分类。若通过页表访问,内核必须首先安排物理地址使其对设备驱动程序可见,在进行任何 I/O 之前必须调用
ioremap。若不通过页表,I/O 内存区域就类似I/O 端口,可以使用适当形式的函数访问它们。因为“side effect”的影响,[color="#000000"]不管是否需要 ioremap ,都不鼓励直接使用 I/O 内存的指针。而使用专用的 I/O 内存操作函数,不仅在所有平台上是安全,而且对直接使用指针操作 I/O 内存的情况进行了优化。
[color="#0000ff"] I/O 内存分配和映射
I/O 内存区域使用前必须先分配,函数接口在 [color="#0000ff"] 定义:
struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);/* 从 start 开始,分配一个 len 字节的内存区域。成功返回一个非NULL指针,否则返回NULL。所有的 I/O 内存分配情况都 /proc/iomem 中列出。*/
/*I/O内存区域在不再需要时应当释放*/
void release_mem_region(unsigned long start, unsigned long len);
/*一个旧的检查 I/O 内存区可用性的函数,不推荐使用*/
int check_mem_region(unsigned long start, unsigned long len);
然后必须设置一个映射,由 ioremap 函数实现,此函数专门用来为I/O
内存区域分配虚拟地址。经过ioremap 之后,设备驱动即可访问任意的 I/O 内存地址。注意:ioremap
返回的地址不应当直接引用;应使用内核提供的 accessor 函数。以下为函数定义:
#include asm/io.h>
void *ioremap(unsigned long phys_addr, unsigned long size);
void *ioremap_nocache(unsigned long phys_addr, unsigned long size);/*如果控制寄存器也在该区域,应使用的非缓存版本,以实现side effect。*/
void iounmap(void * addr);
[color="#0000ff"]访问I/O 内存
访问I/O 内存的正确方式是通过一系列专用于此目的的函数(在 [color="#0000ff"] 中定义的):
/*I/O 内存读函数*/
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
/*addr 是从 ioremap 获得的地址(可能包含一个整型偏移量), 返回值是从给定 I/O 内存读取的值*/
/*对应的I/O 内存写函数*/
void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);
/*读和写一系列值到一个给定的 I/O 内存地址,从给定的 buf 读或写 count 个值到给定的 addr */
void ioread8_rep(void *addr, void *buf, unsigned long count);
void ioread16_rep(void *addr, void *buf, unsigned long count);
void ioread32_rep(void *addr, void *buf, unsigned long count);
void iowrite8_rep(void *addr, const void *buf, unsigned long count);
void iowrite16_rep(void *addr, const void *buf, unsigned long count);
void iowrite32_rep(void *addr, const void *buf, unsigned long count);
/*需要操作一块 I/O 地址,使用一下函数*/
void memset_io(void *addr, u8 value, unsigned int count);
void memcpy_fromio(void *dest, void *source, unsigned int count);
void memcpy_toio(void *dest, void *source, unsigned int count);
/*旧函数接口,仍可工作, 但不推荐。*/
unsigned readb(address);
unsigned readw(address);
unsigned readl(address);
void writeb(unsigned value, address);
void writew(unsigned value, address);
void writel(unsigned value, address);
[color="#0000ff"]像 I/O 内存一样使用端口
一些硬件有一个有趣的特性:一些版本使用 I/O 端口,而其他的使用 I/O 内存。为了统一编程接口,使驱动程序易于编写,2.6 内核提供了一个ioport_map函数:
void *ioport_map(unsigned long port, unsigned int count);/*重映射 count 个I/O 端口,使其看起来像 I/O 内存。,此后,驱动程序可以在返回的地址上使用 ioread8 和同类函数。其在编程时消除了I/O 端口和I/O 内存的区别。
/*这个映射应当在它不再被使用时撤销:*/
void ioport_unmap(void *addr);
/*注意:I/O 端口仍然必须在重映射前使用 request_region 分配I/O 端口。ARM9不支持这两个函数!*/
上面是基于[color="#0000ff"]《Linux设备驱动程序(第3版)》的介绍,以下分析 ARM9的s3c2440A的linux驱动接口。
[color="#0000ff"]ARM9的linux驱动接口
[color="#000000"]s3c24x0处理器是使用I/O内存的,也就是说:他们的外设接口是通过读写相应的寄存器实现的,这些寄存器和内存是使用单一的地址空间,并使用和读写内存一样的指令。所以[color="#ff0000"]推荐使用I/O内存的相关指令。
[color="#000000"]但这并不表示I/O端口的指令在s3c24x0中不可用。但是只要你注意其源码,你就会发现:其实I/O端口的指令只是一个外壳,内部还是使用和I/O内存一样的代码。以下列出一些:
I/O端口
#define outb(v,p)        __raw_writeb(v,__io(p))
#define outw(v,p)        __raw_writew((__force __u16) \
                    cpu_to_le16(v),__io(p))
#define outl(v,p)        __raw_writel((__force __u32) \
                    cpu_to_le32(v),__io(p))
#define inb(p)    ({ __u8 __v = __raw_readb(__io(p)); __v; })
#define inw(p)    ({ __u16 __v = le16_to_cpu((__force __le16) \
            __raw_readw(__io(p))); __v; })
#define inl(p)    ({ __u32 __v = le32_to_cpu((__force __le32) \
            __raw_readl(__io(p))); __v; })
I/O内存
#define ioread8(p)    ({ unsigned int __v = __raw_readb(p); __v; })
#define ioread16(p)    ({ unsigned int __v = le16_to_cpu(__raw_readw(p)); __v; })
#define ioread32(p)    ({ unsigned int __v = le32_to_cpu(__raw_readl(p)); __v; })
#define iowrite8(v,p)    __raw_writeb(v, p)
#define iowrite16(v,p)    __raw_writew(cpu_to_le16(v), p)
#define iowrite32(v,p)    __raw_writel(cpu_to_le32(v), p)
我对I/O端口的指令和I/O内存的指令都写了相应的驱动程序,都通过了测试。[color="#0000ff"]在这里值得注意的有4点:
(1)所有的读写指令所赋的地址必须都是虚拟地址,你有两种选择:使用内核已经定
义好的地址,如
S3C2440_GPJCON等等,这些都是内核定义好的虚拟地址,有兴趣的可以看源码。还有一种方法就是使用自己用ioremap映射的虚拟地址。绝对
不能使用实际的物理地址,否则会因为内核无法处理地址而出现oops。
[color="#0000ff"](2)在使用I/O指令时,可以不使用request_region和request_mem_region,而直接使用outb、ioread等指令。因为request的功能只是告诉内核端口被谁占用了,如再次request,内核会制止。
[color="#0000ff"](3)在使用I/O指令时,所赋的地址数据有时必须通过强制类型转换为 unsigned long ,不然会有警告(具体原因请看
[color="#660099"]Linux设备驱动程序学习(7)-内核的数据类型
[color="#0000ff"]) 。虽然你的程序可能也可以使用,但是最好还是不要有警告为妙。
[color="#0000ff"](4)在include\asm-arm\arch-s3c2410\hardware.h中定义了很多io口的操作函数,有需要可以在驱动中直接使用,很方便。
[color="#0000ff"]实验源码:

IO_port.tar.gz

IO_port_test.tar.gz
IO_mem.tar.gz
IO_mem_test.tar.gz
两个模块都实现了阻塞型独享设备的访问控制,并通知内核不支持llseek。具体的测试在IO_port中。
[color="#0000ff"]测试现象如下:
[Tekkaman2440@SBC2440V4]#cd /lib/modules/
[Tekkaman2440@SBC2440V4]#insmod IO_port.ko
[Tekkaman2440@SBC2440V4]#insmod IO_mem.ko
[Tekkaman2440@SBC2440V4]#cat /proc/devices
Character devices:
  1 mem
  2 pty
  3 ttyp
  4 /dev/vc/0
  4 tty
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  7 vcs
10 misc
13 input
14 sound
81 video4linux
89 i2c
90 mtd
116 alsa
128 ptm
136 pts
153 spi
180 usb
189 usb_device
204 s3c2410_serial
251 IO_mem
252 IO_port
253 usb_endpoint
254 rtc
Block devices:
  1 ramdisk
256 rfd
  7 loop
31 mtdblock
93 nftl
96 inftl
179 mmc
[Tekkaman2440@SBC2440V4]#mknod -m 666 /dev/IO_port c 252 0
[Tekkaman2440@SBC2440V4]#mknod -m 666 /dev/IO_mem c 251 0
[Tekkaman2440@SBC2440V4]#cd /tmp/
[Tekkaman2440@SBC2440V4]#./IO_mem_test
io_addr : c485e0d0
IO_mem: the module can not lseek!
please input the command :1
IO_mem: ioctl 1 ok!
please input the command :8
IO_mem: ioctl STATUS ok!current_status=0X1
please input the command :3
IO_mem: ioctl 3 ok!
please input the command :q
[Tekkaman2440@SBC2440V4]#./IO_porttest_sleep &
[Tekkaman2440@SBC2440V4]#./IO_porttest_sleep &
[Tekkaman2440@SBC2440V4]#./IO_porttest_sleep &
[Tekkaman2440@SBC2440V4]#./IO_port_test
IO_port: the module can not lseek!
please input the command :1
IO_port: ioctl 1 ok!
please input the command :8
IO_port: ioctl STATUS ok!current_status=0X1
please input the command :3
IO_port: ioctl 3 ok!
please input the command :8
IO_port: ioctl STATUS ok! current_status=0X3
please input the command :q
[1] Done ./IO_porttest_sleep
[Tekkaman2440@SBC2440V4]#ps
  PID Uid VSZ Stat Command
    1 root 1744 S init
    2 root SW [kthreadd]
    3 root SWN [ksoftirqd/0]
    4 root SW [watchdog/0]
    5 root SW [events/0]
    6 root SW [khelper]
   61 root SW [kblockd/0]
   62 root SW [ksuspend_usbd]
   65 root SW [khubd]
   67 root SW [kseriod]
   79 root SW [pdflush]
   80 root SW [pdflush]
   81 root SW [kswapd0]
   82 root SW [aio/0]
  709 root SW [mtdblockd]
  710 root SW [nftld]
  711 root SW [inftld]
  712 root SW [rfdd]
  746 root SW [kpsmoused]
  755 root SW [kmmcd]
  773 root SW [rpciod/0]
  782 root 1752 S -sh
  783 root 1744 S init
  785 root 1744 S init
  787 root 1744 S init
  790 root 1744 S init
  843 root 1336 S ./IO_porttest_sleep
  844 root 1336 S ./IO_porttest_sleep
  846 root 1744 R ps
[Tekkaman2440@SBC2440V4]#ps
  PID Uid VSZ Stat Command
    1 root 1744 S init
    2 root SW [kthreadd]
    3 root SWN [ksoftirqd/0]
    4 root SW [watchdog/0]
    5 root SW [events/0]
    6 root SW [khelper]
   61 root SW [kblockd/0]
   62 root SW [ksuspend_usbd]
   65 root SW [khubd]
   67 root SW [kseriod]
   79 root SW [pdflush]
   80 root SW [pdflush]
   81 root SW [kswapd0]
   82 root SW [aio/0]
  709 root SW [mtdblockd]
  710 root SW [nftld]
  711 root SW [inftld]
  712 root SW [rfdd]
  746 root SW [kpsmoused]
  755 root SW [kmmcd]
  773 root SW [rpciod/0]
  782 root 1752 S -sh
  783 root 1744 S init
  785 root 1744 S init
  787 root 1744 S init
  790 root 1744 S init
  847 root 1744 R ps
[3] + Done ./IO_porttest_sleep
[2] + Done ./IO_porttest_sleep
[color="#ff0000"]程序是针对2440的,若是用2410只需要改改测试的io口就好了!

回目录 Linux设备驱动程序学习



[color="#295200"]发表于: 2007-11-14,修改于: 2007-11-28 10:39,已浏览1884次,有评论0条
推荐
投诉







网友评论



网友:
本站网友
时间:2007-11-09 15:14:42    IP地址:124.130.130.★
好文章,我是从你的第一章开始看的,今天9号看到你又有新内容,学不少东西,请坚持下去,加油啊





网友:
本站网友
时间:2007-11-14 16:40:21    IP地址:124.130.130.★
很不错啊,我几乎每天都来学习你的经验,感觉收益非浅,加油啊!





网友:
本站网友
时间:2007-11-14 17:24:21    IP地址:124.130.130.★
我觉得最好的方式就是有人交流^_^我很想与你交流,我的QQ32313055





网友:
本站网友
时间:2007-12-07 13:16:52    IP地址:220.181.54.★
我学arm一年多了,可是水平比阁下差远了。看完文章,收获良多,非常感谢!





网友:
本站网友
时间:2007-12-20 09:47:55    IP地址:202.119.113.★
向Tekkaman  Ninja致敬,你的学习态度和学习方式都值得我们借鉴!





网友:
本站网友
时间:2007-12-25 11:58:13    IP地址:61.146.40.★
圣诞快乐!
让我学到了很多东西,





网友:
本站网友
时间:2007-12-25 13:58:12    IP地址:221.15.9.★
很好的文章啊,我正在学习驱动开发,刚刚入门,谢谢啊





网友:
bxfqing
时间:2008-01-03 16:56:27    IP地址:61.152.238.★
我才开始接触ARM-`入门中.........
期待与你交流下,
e-mail : aravarav@sina.com





网友:
本站网友
时间:2008-01-04 11:19:07    IP地址:58.45.191.★
好 期待





网友:
本站网友
时间:2008-01-04 12:00:28    IP地址:59.40.39.★
write error! code=-1
write ok! code=21
read ok! code=20
[0]=0 [1]=0 [2]=1 [3]=2 [4]=3
[5]=4 [6]=5 [7]=6 [8]=7 [9]=8
[10]=9 [11]=10 [12]=11 [13]=12 [14]=13
[15]=14 [16]=15 [17]=16 [18]=17 [19]=18





网友:
本站网友
时间:2008-01-04 12:09:57    IP地址:59.40.39.★
我改了一下makefile在pc上运行是这个结果
[color="#ff6600"]Blog作者的回复:
所有的源码都在友善之臂SBC2440V4上反复测试过(内核为2.6.22.2)。所以基本上只要改Makefile就好(除了硬件相关的部分)。如果有问题可以将详细的情况发邮件给我,有空我会回复的。





网友:
本站网友
时间:2008-01-30 11:09:31    IP地址:218.79.242.★
不错,支持





网友:
本站网友
时间:2008-02-29 17:11:19    IP地址:202.114.212.★
写的真的很好
我们老师就让写一个字符设备驱动程序,我都是通过在你的网页上学会的所有的东西。





网友:
sailor
时间:2008-03-16 15:07:25    IP地址:219.239.31.★
赞一个,总结的很好
曾经研究过ldd3,没有认真写下读书笔记,以后还得好好再看下了





网友:
本站网友
时间:2008-03-28 16:21:18    IP地址:218.88.106.★
请问怎么使用drivers/spi/atmel_spi.c提供的驱动呢?在/dev下没有相应的设备节点,怎么才能在用户空间访问设备?如果自己写一个驱动注册一个spi驱动可以使用其中哪些函数接口?诚请帮忙
[color="#ff6600"]Blog作者的回复:
你好,atmel的芯片我还没移植过,里面的设置我也不熟,
至于SPI,推荐你参考以下两个帖子看看:
http://www.linuxforum.net/forum/showflat.php?Cat=&Board=embedded&Number=665008&page=7&view=collapsed&sb=5&o=0&fpart=
http://www.linuxforum.net/forum/gshowflat.php?Cat=&Board=embedded&Number=646262&page=1&view=collapsed&sb=5&o=all&fpart=





网友:
本站网友
时间:2008-04-14 20:02:47    IP地址:61.157.97.★
今天看了你的文章很感慨,希望能共同学习。有个问题想问,
请问当insmod scull.ko scull_quantum=6 scull_qset=2
数据已经溢出了,为什么20个数据还能写进去。谢谢了
[color="#ff6600"]Blog作者的回复:
这个和scull的数据结构有关,你认真看看《Linux设备驱动程序学习(1)-字符设备驱动程序》,中的图,你可能对scull的数据结构还没有理解
透,scull_quantum代表了一个quantum的字节数,scull_qset代表一个qset包含几个quantum,但是还有qset没有
限制,也就是说我写20个字节进去,一个scull中有2个qset。





网友:
本站网友
时间:2008-05-22 14:53:06    IP地址:211.100.22.★
总结的太好了。
有一个问题想请教一下。看模块中定义了一个char *whom = "world"
这是一个字符串常量,如果按照标准C的话,指针指向的是一个常量字符串才对,其中的内容不能修改。比如whom[2]='a';这样会有段错误,为什么在内核中这样操作是合法的呢?
[color="#ff6600"]Blog作者的回复:
我觉得你这样是定义一个字符串变量,所以可以修改。我学硬件的,对c研究还不深,不知见解是否正确?





网友:
nighteblis
时间:2008-05-29 11:08:30    IP地址:65.113.40.★
lz的经验和态度值得我们学习,向你致敬!





网友:
本站网友
时间:2008-06-03 20:59:00    IP地址:123.115.206.★
很不错,尤其是有源码和测试程序。





网友:
本站网友
时间:2008-08-22 12:31:10    IP地址:219.133.0.★
你好!刚接触linux驱动,看到你这里的设备模型分析,很不错
帮了我的大忙!
我有一个问题,对于nand flash,作为一个块设备,应用程序如何去访问它,对它进行擦除、读写等操作?
因为它不象字符设备那样有可供调用的系统调用
[color="#ff6600"]Blog作者的回复:
块设备我暂时还没看,见谅





网友:
本站网友
时间:2008-08-25 11:05:48    IP地址:211.69.199.★
我也想知道块设备的一些知识,老是来你这儿看,希望能得到一些帮助,呵呵
[color="#ff6600"]Blog作者的回复:
块设备我还没看啊,苦于没时间和没板做实验





网友:
本站网友
时间:2008-08-26 22:00:21    IP地址:58.33.68.★
问一个问题:
你上文提到的测试程序是不是相当于linux系统的应用程序,实现对驱动的系统调用?
如果是这样,那么测试程序应该放在什么路径下,用什么编译器进行编译,因为我看到你有写测试程序的makefile,是不是也要用交叉编译器对它进行编译阿,可是这样的话,生成的目标代码是arm格式的,不能在pc上直接执行。
麻烦你帮我解答一下这个疑问。
刚刚接触linux,问得问题比较愚蠢,见笑了
[color="#ff6600"]Blog作者的回复:
测试程序是应用程序,在板上运行
要交叉编译





网友:
cursor03
时间:2008-09-12 00:28:28    IP地址:202.103.241.★
总结得很好   就像上完课了要复习  





网友:
本站网友
时间:2008-09-16 10:14:04    IP地址:198.168.27.★
很好的学习总结,受益匪浅!





网友:
guohua219
时间:2008-09-19 12:06:44    IP地址:221.131.9.★
[root@(none) scullc]# insmod scullc.ko
Using scullc.ko
nfs: server 222.31.45.9 not responding, still trying
nfs: server 222.31.45.9 not responding, still trying
nfs: server 222.31.45.9 not responding, still trying
我按照傅大哥写的模块,一装载的时候就出错了........
里面的网络没问题,就是装载了模块后出问题的........
傅大哥知道怎么回事吗???????





网友:
gmkernel
时间:2008-09-20 16:56:57    IP地址:211.68.3.★
佩服你的毅力





网友:
本站网友
时间:2008-09-22 21:08:40    IP地址:58.40.69.★
我是刚接触linux的。今天看到你的总接非常高兴,做的太详细了。。。
我想问一下,是否可以讲解一下container_of是怎么用的。。
谢谢!





网友:
yixilee
时间:2008-11-14 10:06:51    IP地址:61.183.207.★
关于楼主描述的:
最关键的“syslog(8,NULL,level)”语句我不理解,没有找到相关资料。但是通过在ARM9板上的实验表明:程序是ok的!我用Hello world模块做了实验,现象和书上的一致。
我查阅了一下,应该这么理解:
原型是:int syslog(int type, char *bufp, int len);
根据type来采取对内核ring buffer的操作,并设置consele_loglevel的值为len。
type参数有以下几种值:
       /*
        * Commands to sys_syslog:
        *
        *      0 -- Close the log.  Currently a NOP.
        *      1 -- Open the log. Currently a NOP.
        *      2 -- Read from the log.
        *      3 -- Read up to the last 4k of messages in the ring buffer.
        *      4 -- Read and clear last 4k of messages in the ring buffer
        *      5 -- Clear ring buffer.
        *      6 -- Disable printk’s to console
        *      7 -- Enable printk’s to console
        *      8 -- Set level of messages printed to console
        *      9 -- Return number of unread characters in the log buffer
        */
返回值的描述:
       In case of error, -1 is returned, and errno is set. Otherwise,
       for  type  equal  to 2, 3 or 4, syslog() returns the number of
       bytes read, and otherwise 0.
不妥之处请指教,谢谢。
[color="#ff6600"]Blog作者的回复:
谢谢指教!!!





网友:
carlsun22
时间:2008-11-26 16:49:26    IP地址:159.226.124.★
文章写的很好,刚开始学驱动,有很多地方都不是很懂,很想跟你学习啊,想请教一下学习linux设备驱动的方法。





网友:
joely.wu
时间:2008-11-26 17:13:41    IP地址:218.94.130.★
问一个很弱的问题,楼主的源码文件是如何存储在cublog里面的,敬请告知,不胜感激





网友:
zhj1011
时间:2008-11-27 17:14:40    IP地址:221.6.206.★
上传附件





网友:
本站网友
时间:2008-12-04 17:03:58    IP地址:58.211.218.★
write code=6
write code=6
write code=6
write code=2
read code=6
read code=6
read code=6
read code=2
[0]=0 [1]=1 [2]=2 [3]=3 [4]=4
[5]=5 [6]=6 [7]=7 [8]=8 [9]=9
[10]=10 [11]=11 [12]=12 [13]=13 [14]=14
[15]=15 [16]=16 [17]=17 [18]=18 [19]=19
看你的测试程序中 输出语句 要么是 work error! code=..   或者 work ok! code=..    上面的代码输出 work  code=...   既没有error! 也没有ok! 什么原因啊?
在字符驱动那张图 scull_device所指的链表中 Scull_qset 这种结构的个数如何确定呢。
insmod scull_debug.ko scull_nr_devs=1 scull_quantum=6 scull_qset=2后
我测试的结果是:
write error! code=6
write error! code=6
write error! code=6
write ok! code=2
read error! code=6
read error! code=6
read error! code=6
read ok! code=2
[0]=0 [1]=1 [2]=2 [3]=3 [4]=4
[5]=5 [6]=6 [7]=7 [8]=8 [9]=9
[10]=10 [11]=11 [12]=12 [13]=13 [14]=14
[15]=15 [16]=16 [17]=17 [18]=18 [19]=19
请问如何解释啊。





网友:
tuxcn
时间:2008-12-17 23:07:57    IP地址:59.41.253.★
只能说声谢谢你了!





网友:
罗克炬
时间:2008-12-29 18:10:08    IP地址:121.15.214.★
从楼主这里学到不少东西
void kobject_del(struct kobject *kobj); /*是 kobject_del 和 kobject_put 的结合*/写错了





网友:
sosobiao
时间:2009-01-05 18:42:15    IP地址:124.228.114.★
编译的时候出现
[root@localhost scull]# make modules
make -C /home/alu/mywork/systems/linux-2.6.22.6  M=/home/soso/ldd3/scull   modules
make[1]: Entering directory `/home/alu/mywork/systems/linux-2.6.22.6'
  CC [M]  /home/soso/ldd3/scull/scull_t1.o
cc1: invalid option `little-endian'
cc1: invalid option `arm'
cc1: invalid option `apcs'
cc1: invalid option `no-sched-prolog'
cc1: invalid option `apcs-32'
cc1: invalid option `no-thumb-interwork'
cc1: invalid option `tune=arm9tdmi'
cc1: invalid option `alignment-traps'
cc1: unrecognized option `-Wdeclaration-after-statement'
make[2]: *** [/home/soso/ldd3/scull/scull_t1.o] 错误 1
make[1]: *** [_module_/home/soso/ldd3/scull] 错误 2
make[1]: Leaving directory `/home/alu/mywork/systems/linux-2.6.22.6'
make: *** [modules] 错误 2
请教大哥  这个大概可能是什么问题啊  那么多参数无效  
内核版本2.6.22.6
arm-linux-gcc -v
gcc version 3.3.6





网友:
cocobear.info
时间:2009-02-10 11:17:08    IP地址:61.236.244.★
"include/linux/moduleparam.h文件中有关于module_param_array宏的说明,这里第三个参数的作用是:
*nump is set to number they actually specified
所以这里的TNparam_nr并不是限制输入数组参数的数目,而是实际数据的大小。
可以在上面的示例代码中加一句:
printk(KERN_ALERT "TNparam_nr=%d\n",TNparam_nr);
得到的结果会是你实际输入的数组元素个数。
BTW:笔记写得这么详细,我都懒得写了,直接收藏你的笔记了:-)
[color="#ff6600"]Blog作者的回复:
感谢兄台指点!!!!!!!
               
               
               
               
               

本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u3/93893/showart_1882625.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP