嵌入式linux中网贷申请很频繁的后果调用sync函数会产生什么后果

下次自动登录
现在的位置:
& 综合 & 正文
嵌入式Linux驱动程序设计
系统中,正是有了驱动才使得用户可以完全透明的使用计算机系统。设备驱动隐藏了硬件设备的具体的细节和功能,对于不同的硬件设备都提供了一致的接口。比如在
系统中,为了便于用户的使用,系统把计算机系统的各种设备映射成一系列的特殊的设备文件,叫设备文件节点。用户可以任意的使用它来协助自己完成任何工作。在
系统中,所有的设备都可归为三类之列,一是字符设备,如键盘等;二是块设备,如磁盘等;第三种就是用于网络的网络设备,这类设备主要是用于网络通讯中,在其他的地方没有什么特别使用。
设备驱动程序的三个主要组成部分
自动配置和初始化子程序;
提供服务的子程序;
中断子程序;
设备驱动入口点分析
此处以字符设备为例列举设备驱动程序的入口点
入口点:设备
操作之前进行的操作,打开了设备后准备
入口点:对设备操作完成之后对设备的关闭操作;
入口点:完成对设备的
操作之后就可以以此入口点在设备上读取数据;
入口点:完成对设备的
操作之后就可以以此入口点在设备上写入数据;
入口点:在设备上执行
控制操作;
入口点:在数据写入或读取之前对设备的检测操作。
系统下的设备驱动程序
系统内核的来分析字符设备驱动程序
、以下是入口点的结构体定义:
#/usr/include/linux/fs.h
file_operations {
struct module *
loff_t (*llseek) (struct file *, loff_t,
ssize_t (*read) (struct file *, char *,
size_t, loff_t *);
ssize_t (*write) (struct file *, const char
*, size_t, loff_t *);
int (*readdir) (struct file *, void *,
filldir_t);
unsigned int (*poll) (struct file *, struct
poll_table_struct *);
int (*ioctl) (struct inode *, struct file
*, unsigned int, unsigned long);
int (*mmap) (struct file *, struct
vm_area_struct *);
int (*open) (struct inode *, struct file
int (*flush) (struct file *);
(*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry
*, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct
file_lock *);
ssize_t (*readv) (struct file *, const
struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const
struct iovec *, unsigned long, loff_t *);
ssize_t (*sendpage) (struct file *, struct
page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct
file *, unsigned long, unsigned long, unsigned long, unsigned long);
file_operations
,移动文件指针的位置,用于可随机存取的设备;
,设备读操作,参数
指向的是存放读取结果的缓冲池,
为将要读取的数据的长度。若函数返回值为负,表示读取操作出错;若操作成功,则返回实际的读取的字节;
,设备写操作;
,进行除设备的读写之外的控制操作;
,打开一个设备,并对其
操作准备。函数返回值为
表示设备打开成功,若返回负,表示操作失败;
系统下,字符设备驱动的注册:
里通过调用
register_chrdev
向操作系统注册字符型的设备驱动,其定义为:
register_chrdev(unsigned int major, const char *name,
const struct file_operations
参数说明:
是设备驱动程序注册的主设备号,若为
则系统为此驱动程序动态地分配一个主设备号。
是设备文件节点名。此函数返回
表示成功。返回
表示申请的主设备号不合法,一般来说是因为主设备号大于系统所允许的最大设备号。返回
表示所申请的主设备号正在被其它设备驱动程序使用。如果是动态分配主设备号成功,此函数将返回所分配的主设备号。如果
register_chrdev
操作成功,设备名就可以在
/proc/devices
文件里看到。
这里看到的就是一段设备驱动经过注册之后在
/proc/devices
下面看到的结果
字符设备驱动程序示例及其分析
//#define CONFIG_DEVFS_FS
系统是否支持
#ifndef __KERNEL__
编译进内核
# define __KERNEL__
#ifndef MODULE
define MODULE
以模块方式编译
#include &linux/config.h&
配置头文件
#include &linux/module.h&
驱动模型头文件
&linux/devfs_fs_kernel.h&
设备文件系统头文件
#include &linux/init.h&
初始化相关头文件
#include &linux/kernel.h&
等函数有关的头文件
#include &linux/slab.h&
等函数有关的头文件
#include &linux/fs.h&
与文件系统有关的头文件
everything... */
#include &linux/errno.h&
错误代码处理头文件
error codes */
#include &linux/types.h&
数据类型头文件
#include &linux/proc_fs.h&
与进程调度相关的头文件
#include &linux/fcntl.h&
/* O_ACCMODE */
#include &linux/poll.h&
/* COPY_TO_USER */
#include &asm/system.h&
/* cli(), *_flags */
#define DEVICE_NAME
该驱动的设备名
#define demo_MAJOR 254
驱动主设备号
#define demo_MINOR 0
驱动次设备号
static int MAX_BUF_LEN=1024;
定义一缓冲区最大长度
static char drv_buf[1024];
定义一缓冲区
static int WRI_LENGTH=0;
/***********************************************************************
demo_write
功能:对应用户空间的
系统调用,从用户空间拷贝给定长度缓冲区数据到内核空间
入口参数:
操作设备文件的
对应用户空间的缓冲区的起始地址,
用户空间数据缓冲区长度
出口参数:返回用户空间数据缓冲区长度
**********************************************************************/
static ssize_t
demo_write(struct file *filp,const char
*buffer, size_t count)
& MAX_BUF_LEN)count = MAX_BUF_LEN;
copy_from_user(drv_buf
, buffer, count);
从用户空间拷贝缓冲区数据到内核空间的关键函数,把用户空间的
数据传递给
WRI_LENGTH
printk("user
write data to driver/n");
do_write();
/***********************************************************************
static void do_write()
入口参数:无
出口参数:无
**********************************************************************/
static void do_write()
len = WRI_LENGTH;
= 0; i & (len&&1); i++,len--){
= drv_buf[len-1];
drv_buf[len-1]
= drv_buf[i];
drv_buf[i]
/*************************************************************
demo_ read
功能:对应用户空间的
系统调用,从内核空间拷贝给定长度缓冲区数据到用户空间
入口参数:
操作设备文件的
对应用户空间的缓冲区的起始地址,
用户空间数据缓冲区长度,
用户在文件中进行存储操作的位置
出口参数:返回用户空间数据缓冲区长度
**********************************************************************/
static ssize_t
demo_read(struct file *filp, char *buffer,
size_t count, loff_t *ppos)
& MAX_BUF_LEN)
count=MAX_BUF_LEN;
copy_to_user(buffer, drv_buf,count);
从内核空间拷贝缓冲区数据到用户空间的关键函数,把内核空间经过逆序排列后的
数组传递给用户空间的
printk("user
read data from driver/n");
/***********************************************************************
demo_ioctl
功能:对应用户空间的
系统调用,对用户空间传递过来的命令进行
判断,并进行相应处理,本函数只对用户空间传递过来的
做简单的处理,打印一条信息
入口参数:
操作设备文件的
对应用户空间的
对应用户空间传递过来的参数
出口参数:正确返回
,错误命令返回
的提示内容
**********************************************************************/
static int demo_ioctl(struct inode *inode,
struct file *file,
unsigned int cmd, unsigned
switch(cmd){
1:printk("runing command 1 /n");
2:printk("runing command 2 /n");
printk("error
cmd number/n");
/***********************************************************************
static void demo_open ()
功能:设备文件打开函数,对应用户空间
系统调用,
入口参数:设备文件节点
出口参数:无
**********************************************************************/
static int demo_open(struct inode *inode,
struct file *file)
MOD_INC_USE_COUNT;
printk("device
open sucess!/n");
static int
demo_release
功能:设备文件释放函数,对应用户空间
系统调用,
入口参数:设备文件节点
出口参数:无
**********************************************************************/
static int
demo_release(struct inode *inode, struct file *filp)
MOD_DEC_USE_COUNT;
printk("device
release/n");
&&&&推荐文章:
【上篇】【下篇】您的位置: &&
嵌入式LINUX就业班
广州哪里有学嵌入式ARM9驱动
价格:详询
课时:详询
班制:周末班&&&&
上课地点:广州市科学城光谱西路69号TCL文化产业园二期创意中心B栋4&&&&
开课学校:
学校信誉:
课程热线:020-
课程评分:4.5分
我想学的是:> 博客详情
VFS(Virtual File System)的存在使得Linux可以兼容不同的文件系统,例如ext3、ext4、xfs、ntfs等等,其不仅具有为所有的文件系统实现一个通用的 外接口的作用,还具有另一个与系统性能相关的重要作用——缓存。VFS中引入了高速磁盘缓存的机制,这属于一种软件机制,允许内核将原本存在磁盘上的某些 信息保存在RAM中,以便对这些数据的进一步访问能快速进行,而不必慢速访问磁盘本身。高速磁盘缓存可大致分为以下三种:
目录项高速缓存——主要存放的是描述文件系统路径名的目录项对象
索引节点高速缓存——主要存放的是描述磁盘索引节点的索引节点对象
页高速缓存——主要存放的是完整的数据页对象,每个页所包含的数据一定属于某个文件,同时,所有的文件读写操作都依赖于页高速缓存。其是Linux内核所使用的主要磁盘高速缓存。
正是由于缓存的引入,所以VFS文件系统采用了文件数据延迟写的技术,因此,如果在调用系统接口写入数据时没有使用同步写模式,那么大多数据将会先保存在缓存中,待等到满足某些条件时才将数据刷入磁盘里。
内核是如何将数据刷入磁盘的呢?
何时把脏页写入磁盘
内核不断用包含块设备数据的页填充页高速缓存。只要进程修改了数据,相应的页就被标记为脏页,即把它的PG_dirty标志位置。
& & Unix系统允许把脏缓冲区写入块设备的操作延迟执行,因为这种策略可以显著地提高系统的性能。对高速缓存中的页的几次写操作可能只需对相应的磁盘块进行 一次缓慢的物理更新就可以满足。此外,写操作没有读操作那么紧迫,因为进程通常是不会因为延迟写而挂起,而大部分情况都因为延迟读而挂起。
一个脏页可能直到最后一刻(即直到系统关闭时)都一直逗留在主存中。然而,从延迟写策略的局限性来看,它有两个主要的缺点:
& 一、如果发生了硬件错误或者电源掉电的情况,那么就无法再获得RAM的内容,因此,从系统启动以来对文件进行的很多修改就丢失了。
& 二、页高速缓存的大小(由此存放它所需的RAM的大小)就可要很大——至少要与所访问块设备的大小不同。
因此,在下列条件下把脏页刷新(写入)到磁盘:
页高速缓存变得太满,但还需要更多的页,或者脏页的数量已经太多。
自从页变成脏页以来已过去太长时间。
进程请求对块设备或者特定文件任何待定的变化都进行刷新。通过调用sync()、fsync()或者fdatasync()系统调用来实现。
缓冲区页的 引入是问题更加复杂。与每个缓冲区页相关的缓冲区首部使内核能够了解每个独立块缓冲区的状态。如果至少有一个缓冲区首部的PG_Dirty标志被置位,就 应该设置相应缓冲区页的PG_dirty标志。当内核选择要刷新的缓冲区时,它扫描相应的缓冲区首部,并只把脏块的内容有效的写到磁盘。一旦内核把缓冲区 的所有脏页刷新到磁盘,就把页的PG_dirty标志清0。
谁来把脏页写入磁盘
由pdflush内核线程负责。早期版本的Linux使用bdfllush内核线程系统地扫描页高速缓存以搜索要刷新的脏页,并且使用另一个内核线程kupdate来保证所有的页不会“脏”太长时间。Linux 2.6用一组通用内核线程pdflush替代上述两个线程。当系统没有要刷新的脏页时,pdflush线程会自动处于睡眠状态,最后由pdflush_operation()函数来唤醒。
在下面几种情况下,系统会唤醒pdflush回写脏页:
1 、定时方式:&&&& 定时机制定时唤醒pdflush内核线程,周期为/proc/sys/vm/dirty_writeback_centisecs ,单位是(1/100)秒,每次周期性唤醒的pdflush线程并不是回写所有的脏页,而是只回写变脏时间超过/proc/sys/vm/dirty_expire_centisecs(单位也是1/100秒)。注意:变脏的时间是以文件的inode节点变脏的时间为基准的,也就是说如果某个inode节点是10秒前变脏的,pdflush就认为这个inode对应的所有脏页的变脏时间都是10秒前,即使可能部分页面真正变脏的时间不到10秒,细节可以查看内核函数wb_kupdate()。
2、 内存不足的时候:&&& 这时并不将所有的dirty页写到磁盘,而是每次写大概1024个页面,直到空闲页面满足需求为止。3 、写操作时发现脏页超过一定比例:&&& 当脏页占系统内存的比例超过/proc/sys/vm/dirty_background_ratio 的时候,write系统调用会唤醒pdflush回写dirty page,直到脏页比例低于/proc/sys/vm/dirty_background_ratio,但write系统调用不会被阻塞,立即返回。当脏页占系统内存的比例超过/proc/sys/vm/dirty_ratio的时候, write系统调用会被被阻塞,主动回写dirty page,直到脏页比例低于/proc/sys/vm/dirty_ratio,这一点在2.4内核中是没有的。4 、用户调用sync系统调用:&&& 这是系统会唤醒pdflush直到所有的脏页都已经写到磁盘为止。
linux系统在向存储设备上写数据的时候,其实,数据没有被立即写入到物理设备上,而一般处理过程是:
调用fwrite()将数据写入文件缓冲区(用户态进程的buffer)。
进程定期调用fflush()函数之后,把文件缓冲区中的文件数据写到文件系统中,此时数据还没有被真正写入到物理介质中。
fsync(fileno(fp))。该函数返回后,才能保证写入到了物理介质上。即先调用获得文件描述符之后,再调用函数返回后才将文件写入到物理介质上。
fflush和fsync的一些总结
1.提供者fflush是libc.a中提供的方法,fsync是linux系统内核提供的系统调用。2.原形fflush接受一个参数FILE *.fflush(FILE *);fsync接受的时一个Int型的文件描述符。fsync(int fd);3.功能fflush:是把C库中的缓冲调用write函数写到磁盘[其实是写到内核的缓冲区]。fsync:是把内核缓冲刷到磁盘上。4.fsync 将文件相关的所有更改都发送到disk device。 这个调用是阻塞的,直到disk通知此函数传输完成。此函数也会将该文件的文件信息flush到disk。5.fsync最终将缓冲的数据更新到文件里。所以可以看出fflush和fsync的调用顺序应该是:c库缓冲-----fflush---------〉内核页高速缓存--------fsync-----〉磁盘
与文件读写相关的几个重要概念
脏页:linux内核中的概念,因为硬盘的读写速度远赶不上内存的速度,系统就把读写比较频繁的数据事先放到内存中,以提高读写速度,这就叫高速缓存,linux是以页作为高速缓存的单位,当进程修改了高速缓存里的数据时,该页就被内核标记为脏页,内核将会在合适的时间把脏页的数据写到磁盘中去,以保持高速缓存中的数据和磁盘中的数据是一致的
内存映射:内存映射文件,是由一个文件到一块内存的映射。Win32提供了允许应用程序把文件映射到一个进程的函数 (CreateFileMapping)。内存映射文件与虚拟内存有些类似,通过内存映射文件可以保留一个地址空间的区域,同时将物理存储器提交给此区域,内存文件映射的物理存储器来自一个已经存在于磁盘上的文件,而且在对该文件进行操作之前必须首先对文件进行映射。使用内存映射文件处理存储于磁盘上的文件时,将不必再对文件执行I/O操作,使得内存映射文件在处理大数据量的文件时能起到相当重要的作用。延迟写(delayed write):传统的UNIX实现在内核中设有缓冲区高速缓存或页面高速缓存,大多数磁盘I/O都通过缓冲进行。当将数据写入文件时,内核通常先将该数据复制到其中一个缓冲区中,如果该缓冲区尚未写满,则并不将其排入输出队列,而是等待其写满或者当内核需要重用该缓冲区以便存放其他磁盘块数据时,再将该缓冲排入输出队列,然后待其到达队首时,才进行实际的I/O操作。这种输出方式被称为延迟写(delayed write)(Bach [1986]第3章详细讨论了缓冲区高速缓存)。延迟写减少了磁盘读写次数,但是却降低了文件内容的更新速度,使得欲写到文件中的数据在一段时间内并没有写到磁盘上。当系统发生故障时,这种延迟可能造成 文件更新内容的丢失。为了保证磁盘上实际文件系统与缓冲区高速缓存中内容的一致性,UNIX系统提供了sync、fsync和fdatasync三个函数。sync函数只是将所有修改过的块缓冲区排入写队列,然后就返回,它并不等待实际写磁盘操作结束。通常称为update的系统守护进程会周期性地(一般每隔30秒)调用sync函数。这就保证了定期冲洗内核的块缓冲区。命令sync(1)也调用sync函数。fsync函数只对由文件描述符filedes指定的单一文件起作用,并且等待写磁盘操作结束,然后返回。fsync可用于数据库这样的应用程序,这种应用程序需要确保将修改过的块立即写到磁盘上。fdatasync函数类似于fsync,但它只影响文件的数据部分。而除数据外,fsync还会同步更新文件的属性。
对于提供事务支持的数据库,在事务提交时,都要确保事务日志(包含该事务所有的修改操作以及一个提交记录)完全写到硬盘上,才认定事务提交成功并返回给应用层。
一个简单的问题:在*nix操作系统上,怎样保证对文件的更新内容成功持久化到硬盘?
1. &write不够,需要fsync
一般情况下,对硬盘(或者其他持久存储 设备)文件的write操作,更新的只是内存中的页缓存(page cache),而脏页面不会立即更新到硬盘中,而是由操作系统统一调度,如由专门的flusher内核线程在满足一定条件时(如一定时间间隔、内存中的脏 页达到一定比例)内将脏页面同步到硬盘上(放入设备的IO请求队列)。
因为write调用不会等到硬盘IO完 成之后才返回,因此如果OS在write调用之后、硬盘同步之前崩溃,则数据可能丢失。虽然这样的时间窗口很小,但是对于需要保证事务的持久化 (durability)和一致性(consistency)的数据库程序来说,write()所提供的“松散的异步语义”是不够的,通常需要OS提供的同步IO(synchronized-IO)原语来保证:
1&#include&&unistd.h&2&int&fsync(int&fd);
fsync的功能是确保文件fd所有已修改的内容已经正确同步到硬盘上,该调用会阻塞等待直到设备报告IO完成。
PS:如果采用内存映射文件的方式进行文件IO(使用mmap,将文件的page cache直接映射到进程的地址空间,通过写内存的方式修改文件),也有类似的系统调用来确保修改的内容完全同步到硬盘之上:
1 #incude &sys/mman.h&2 int msync(void *addr, size_t length, int flags)
msync需要指定同步的地址区间,如此细粒度的控制似乎比fsync更加高效(因为应用程序通常知道自己的脏页位置),但实际上(Linux)kernel中有着十分高效的数据结构,能够很快地找出文件的脏页,使得fsync只会同步文件的修改内容。
2. fsync的性能问题,与fdatasync
除了同步文件的修改内容(脏页),fsync还会同步文件的描述信息(metadata,包括size、访问时间st_atime & st_mtime等等),因为文件的数据和metadata通常存在硬盘的不同地方,因此fsync至少需要两次IO写操作,fsync的man page这样说:
"Unfortunately&fsync()&will always initialize two write operations : one for the newly written data and another one in order to update the modification time stored in the inode. If the modification time is not a part of the transaction concept&fdatasync()&can be used to avoid unnecessary inode disk write operations."
多余的一次IO操作,有多么昂贵呢?根据,当前硬盘驱动的平均寻道时间(Average seek time)大约是3~15ms,7200RPM硬盘的平均旋转延迟(Average rotational latency)大约为4ms,因此一次IO操作的耗时大约为10ms左右。这个数字意味着什么?下文还会提到。
Posix同样定义了fdatasync,放宽了同步的语义以提高性能:
1&#include&&unistd.h&2&int&fdatasync(int&fd);
fdatasync的功能与fsync类似,但是仅仅在必要的情况下才会同步metadata,因此可以减少一次IO写操作。那么,什么是“必要的情况”呢?根据man page中的解释:
"fdatasync does not flush modified metadata unless that metadata is needed in order to allow a subsequent data retrieval to be corretly handled."
举例来说,文件的尺寸(st_size)如果变化,是需要立即同步的,否则OS一旦崩溃,即使文件的数据部分已同步,由于metadata没有同步,依然读不到修改的内容。而最后访问时间(atime)/修改时间(mtime)是不需要每次都同步的,只要应用程序对这两个时间戳没有苛刻的要求,基本无伤大雅。
PS:open时的参数O_SYNC/O_DSYNC有着和fsync/fdatasync类似的语义:使每次write都会阻塞等待硬盘IO完成。(实际上,Linux对O_SYNC/O_DSYNC做了相同处理,没有满足Posix的要求,而是都实现了fdatasync的语义)相对于fsync/fdatasync,这样的设置不够灵活,应该很少使用。
3. 使用fdatasync优化日志同步
文章开头时已提到,为了满足事务要求,数据库的日志文件是常常需要同步IO的。由于需要同步等待硬盘IO完成,所以事务的提交操作常常十分耗时,成为性能的瓶颈。
在Berkeley DB下,如果开启了AUTO_COMMIT(所有独立的写操作自动具有事务语义)并使用默认的同步级别(日志完全同步到硬盘才返回),写一条记录的耗时大约为5~10ms级别,基本和一次IO操作(10ms)的耗时相同。
&我们已经知道,在同步上fsync是低效的。但是如果需要使用fdatasync减少对metadata的更新,则需要确保文件的尺寸在write前后没有发生变化。日志文件天生是追加型(append-only)的,总是在不断增大,似乎很难利用好fdatasync。
且看Berkeley DB是怎样处理日志文件的:
1.每个log文件固定为10MB大小,从1开始编号,名称格式为“log.%010d"
2.每次log文件创建时,先写文件的最后1个page,将log文件扩展为10MB大小
3.向log文件中追加记录时,由于文件的尺寸不发生变化,使用fdatasync可以大大优化写log的效率
4.如果一个log文件写满了,则新建一个log文件,也只有一次同步metadata的开销
人打赏支持
码字总数 22525
支付宝支付
微信扫码支付
打赏金额: ¥
已支付成功
打赏金额: ¥基于S3C2440的嵌入式Linux驱动——SPI子系统解读(四) - c-struct-s3c2440 -
基于S3C2440的嵌入式Linux驱动——SPI子系统解读(四)
相关推荐:该系列文章将分为四个部分:
第一部分,将对SPI子系统整体进行描述,同时给出SPI的相关数据结构,最后描述SPI总线的注册。基于S3C2440的嵌入式Linux驱动——S
本系列文章对Linux设备模型中的SPI子系统进行讲解。SPI子系统的讲解将分为4个部分。第一部分,将对SPI子系统整体进行描述,同时给出SPI的相关数据结构,最后描述SPI总线的注册。基于S3C2440的嵌入式Linux驱动——SPI子系统解读(一)第二部分,该文将对SPI的主控制器(master)驱动进行描述。基于S3C2440的嵌入式Linux驱动——SPI子系统解读(二)第三部分,该文将对SPI设备驱动,也称protocol 驱动,进行讲解。基于S3C2440的嵌入式Linux驱动——SPI子系统解读(三)第四部分,即本篇文章,通过SPI设备驱动留给用户层的API,我们将从上到下描述数据是如何通过SPI的protocol 驱动,由bitbang 中转,最后由master驱动将数据传输出去。本文属于第四部分。7. write,read和ioctl综述在spi设备驱动层提供了两种数据传输方式。一种是半双工方式,write方法提供了半双工读访问,read方法提供了半双工写访问。另一种就是全双工方式,ioctl调用将同时完成数据的传送与发送。在后面的描述中,我们将对write和ioctl方法做出详细的描述,而read方法和write极其相似,将不多做介绍。接下来首先看看write方法是如何实现的。8. write方法8.1 spidev_write 在用户空间执行open打开设备文件以后,就可以执行write系统调用,该系统调用将会执行我们提供的write方法。代码如下: 下列代码位于drivers/spi/spidev.c中。/* Write-only message with current device setup */static ssize_tspidev_write(struct file *filp, const char __user *buf,size_t count, loff_t *f_pos){ struct spidev_data * ssize_tstatus = 0; unsigned long /* chipselect only toggles at start or end of operation */ if (count & bufsiz) /*数据大于4096字节*/return -EMSGSIZE; spidev = filp-&private_ mutex_lock(&spidev-&buf_lock); /*将用户层的数据拷贝至buffer中,buffer在open方法中分配*/ missing = copy_from_user(spidev-&buffer, buf, count);if (missing == 0) {status = spidev_sync_write(spidev, count); } elsestatus = -EFAULT; mutex_unlock(&spidev-&buf_lock);}在这里,做的事情很少,主要就是从用户空间将需要发送的数据复制过来。然后调用spidev_sync_write。8.2 spidev_sync_write下列代码位于drivers/spi/spidev.c中。static inline ssize_tspidev_sync_write(struct spidev_data *spidev, size_t len){ struct spi_transfer t = {.tx_buf= spidev-&buffer,.len= len,}; struct spi_ spi_message_init(&m); spi_message_add_tail(&t, &m); return spidev_sync(spidev, &m);}static inline void spi_message_init(struct spi_message *m){memset(m, 0, sizeof *m);INIT_LIST_HEAD(&m-&transfers);/*初始化链表头*/}spi_message_add_tail(struct spi_transfer *t, struct spi_message *m){list_add_tail(&t-&transfer_list, &m-&transfers);/*添加transfer_list*/}在这里,创建了transfer和message。spi_transfer包含了要发送数据的信息。然后初始化了message中的transfer链表头,并将spi_transfer添加到了transfer链表中。也就是以spi_message的transfers为链表头的链表中,包含了transfer,而transfer正好包含了需要发送的数据。由此可见message其实是对transfer的封装。最后,调用了spidev_sync,并将创建的spi_message作为参数传入。8.3spidev_sync下列代码位于drivers/spi/spidev.c中。static ssize_tspidev_sync(struct spidev_data *spidev, struct spi_message *message){ DECLARE_COMPLETION_ONSTACK(done); /*创建completion*/ message-&complete = spidev_/*定义complete方法*/ message-&context = &/*complete方法的参数*/ spin_lock_irq(&spidev-&spi_lock); if (spidev-&spi == NULL)status = -ESHUTDOWN; elsestatus = spi_async(spidev-&spi, message);/*异步,用complete来完成同步*/ spin_unlock_irq(&spidev-&spi_lock); if (status == 0) {wait_for_completion(&done); /*在bitbang_work中调用complete方法来唤醒*/status = message-&if (status == 0)status = message-&actual_ /*返回发送的字节数*/ }}在这里,初始化了completion,这个东东将实现write系统调用的同步。在后面我们将会看到如何实现的。随后调用了spi_async,从名字上可以看出该函数是异步的,也就是说该函数返回后,数据并没有被发送出去。因此使用了wait_for_completion来等待数据的发送完成,达到同步的目的。8.4 spi_async下列代码位于drivers/spi/spi.h中。 /** * spi_async - asynchronous SPI transfer * @spi: device with which data will be exchanged * @message: describes the data transfers, including completion callback * Context: any (irqs may be blocked, etc) * * 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 t 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 intspi_async(struct spi_device *spi, struct spi_message *message){message-&spi =/*指出执行transfer的SPI接口*/return spi-&master-&transfer(spi, message);/*即调用spi_bitbang_transfer*/}这个函数仅仅保存了spi_device信息后,然后调用了master的transfer方法,该方法在spi_bitbang_start中定义为spi_bitbang_transfer。8.5 spi_bitbang_transfer下列代码位于drivers/spi/spi_bitbang.c中。 /** * spi_bitbang_transfer - default submit to transfer queue */int spi_bitbang_transfer(struct spi_device *spi, struct spi_message *m){ struct spi_bitbang * unsigned long intstatus = 0; m-&actual_length = 0; m-&status = -EINPROGRESS; bitbang = spi_master_get_devdata(spi-&master); spin_lock_irqsave(&bitbang-&lock, flags); if (!spi-&max_speed_hz)status = -ENETDOWN; else {/*下面的工作队列和queue在spi_bitbang_start函数中初始化*/list_add_tail(&m-&queue, &bitbang-&queue);/*将message添加到bitbang的queue链表中*/queue_work(bitbang-&workqueue, &bitbang-&work);/*提交工作到工作队列*/ } spin_unlock_irqrestore(&bitbang-&lock, flags);}这里将message添加到了bitbang的queue链表中。然后提交了一个工作到工作队列,随后函数返回到spi_async,又返回到spidev_sync中。为方便将spidev_sync的部分代码列出:status = spi_async(spidev-&spi, message);/*异步,用complete来完成同步*/ spin_unlock_irq(&spidev-&spi_lock); if (status == 0) {wait_for_completion(&done); /*在bitbang_work中调用complete方法来唤醒*/status = message-&if (status == 0)status = message-&actual_ /*返回发送的字节数*/ } 当spi_async函数返回后,需要发送的数据已经通过工作的形式添加到了工作队列,在稍后的工作执行时,将完成数据的发送。随后调用了wait_for_completion等待数据的发送完成。到此,可以看出completion的使用是用来完成同步I/O的8.6 bitbang_work在上一节最后添加了工作bitbang-&work到工作队列中,在过一段时候后,内核将以进程执行该work。而work即为在spi_bitbang_start中定义的bitbang_work函数。我们来看下这个函数。下列代码位于drivers/spi/spi_bitbang.c中。 /* * 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 * 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(struct work_struct *work){struct spi_bitbang*bitbang =container_of(work, struct spi_bitbang, work);/*获取spi_bitbang*/unsigned longspin_lock_irqsave(&bitbang-&lock, flags);/*自旋锁加锁*/bitbang-&busy = 1;/*bitbang忙碌*/while (!list_empty(&bitbang-&queue)) {/*有spi_message*/struct spi_message*m;struct spi_device*unsignedstruct spi_transfer*t = NULL;unsignedunsignedcs_intint(*setup_transfer)(struct spi_device *,struct spi_transfer *);m = container_of(bitbang-&queue.next, struct spi_message,/*获取spi_message*/queue);list_del_init(&m-&queue);/*以获取spi_message,删除该spi_message*/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-&tmp = 0;cs_change = 1;status = 0;setup_transfer = NULL;/*遍历,获取所有的spi_transfer*/list_for_each_entry (t, &m-&transfers, transfer_list) {/* override or restore speed and wordsize */if (t-&speed_hz || t-&bits_per_word) { /*如果这两个参数有任何一个已经设置了,本例中没有定义*/setup_transfer = bitbang-&setup_if (!setup_transfer) {status = -ENOPROTOOPT;}}if (setup_transfer) {/*本例中为NULL*/status = setup_transfer(spi, t);if (status & 0)}/* set up default clock polarity,* 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) {/*初值为1*/bitbang-&chipselect(spi, BITBANG_CS_ACTIVE);/*即调用s3c24xx_spi_chipsel,激活CS信号,写寄存器,设置SPI模式*/ndelay(nsecs);/*延迟100纳秒*/}cs_change = t-&cs_/*保存cs_change*/if (!t-&tx_buf && !t-&rx_buf && t-&len) {/*检查参数*/status = -EINVAL;}/* 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;/*不使用DMA*/status = bitbang-&txrx_bufs(spi, t);/*即调用s3c24xx_spi_txrx,开始发送数据,status为已发送数据的大小*/}if (status & 0)m-&actual_length +=/*保存已发送字节*/if (status != t-&len) {/*要求发送和已发送的大小不同*//* always report some kind of error */if (status &= 0)status = -EREMOTEIO;}status = 0;/* protocol tweaks before next transfer */if (t-&delay_usecs)udelay(t-&delay_usecs);/*延迟*/if (!cs_change)/*判断是否需要禁止CS,为1表示要求在两次数据传输之间禁止CS*/if (t-&transfer_list.next == &m-&transfers)/*没有transfer*//* 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);/*禁止CS*/ndelay(nsecs);}/*遍历spi_transfer结束*/m-&status =m-&complete(m-&context); /*调用complete,一个message处理完毕*//* 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);/*禁止CS*/ndelay(nsecs);}spin_lock_irqsave(&bitbang-&lock, flags);}bitbang-&busy = 0;spin_unlock_irqrestore(&bitbang-&lock, flags);}本函数中,调用了两个方法bibang-&chipselect和bitbang-&txrx_bufs,这两个方法实际调用了s3c24xx_spi_chipsel和s3c24xx_spi_txrx函数,这两个函数都是master驱动层提供的函数。s3c24xx_spi_chipsel已经在4.2.2节中给出,该函数设置控制寄存器并激活CS信号。s3c24xx_spi_txrx函数的实参t,即为spi_transfer,函数完成该spi_transfer中数据的发送,并返回已发送的字节数。然后,判断是否需要禁止CS。接着遍历到下一个spi_transfer,再次发送数据。当所有spi_transfer发送完成以后,将调用complete方法,从而让在spidev_sync函数中等待completion的函数返回。下面,先来来看下数据是怎么发送出去的,也就是s3c24xx_spi_txrx函数。最后,看看complete方法。8.7 s3c24xx_spi_txrx 和s3c24xx_spi_irq下列代码位于deivers/spi/s3c24xx.c。static inline unsigned int hw_txbyte(struct s3c24xx_spi *hw, int count){return hw-&tx ? hw-&tx[count] : 0;/*发送缓冲区指针是否为空,空则发送0*/}static int s3c24xx_spi_txrx(struct spi_device *spi, struct spi_transfer *t)/*bitbang.txrx_bufs方法*/{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);/*保存transfer相关数据到s3c24xx_sp结构中*/hw-&tx = t-&tx_hw-&rx = t-&rx_hw-&len = t-&hw-&count = 0;init_completion(&hw-&done);/*初始化completion*//* send the first byte *//*发送第一个数据,tx[0]*/writeb(hw_txbyte(hw, 0), hw-&regs + S3C2410_SPTDAT);wait_for_completion(&hw-&done);/*等待completion*/return hw-&/*返回发送的字节数*/}static irqreturn_t s3c24xx_spi_irq(int irq, void *dev){struct s3c24xx_spi *hw =unsigned int spsta = readb(hw-&regs + S3C2410_SPSTA);/*获取状态寄存器*/unsigned int count = hw-&if (spsta & S3C2410_SPSTA_DCOL) {/*发生错误*/dev_dbg(hw-&dev, &data-collision\n&);complete(&hw-&done);/*唤醒等待complete的进程*/goto irq_}if (!(spsta & S3C2410_SPSTA_READY)) {/*未就绪*/dev_dbg(hw-&dev, &spi not ready for tx?\n&);complete(&hw-&done);/*唤醒等待complete的进程*/goto irq_}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);elsecomplete(&hw-&done);/*发送完毕,唤醒等待complete的进程*/ irq_相关推荐:CPU : s3c2410 PLAT : 由smdk2410修改来的一块板子。(资料依旧非常有限) 这次的项目,板子上面串口部分非常怪异。我们知道,samsung s3c2410这款芯片的UART控done:return IRQ_HANDLED;} 在s3c24xx_spi_txrx函数中,首先发送了待发送数据中的第一个字节,随后就调用wait_for_completion来等待剩余的数据发送完成。NOTE:这里的completion是master驱动层的,spi设备驱动也有一个completion,用于IO同步,不要混淆。当第一个数据发送完成以后,SPI中断产生,开始执行中断服务程序。在中断服务程序中,将判断是否需要读取数据,如果是则从寄存器中读取数据。NOTE:如果是使用read系统调用,那么在此发送的数据将是0。随后发送下一个数据,直到数据发送完成。发送完成后调用complete,使在s3c24xx_spi_txrx的wait_for_completion得以返回。接着,s3c24xx_spi_txrx就将返回已发送的字节数。NOTE:其实该中断服务子程序实现了全双工数据的传输,只不过特定于具体的系统调用,从而分为了半双工读和写。8.8 complete方法在8.6节的bitbang_work中,当一个message的所有数据发送完成以后,将会调用complete函数。该函数如下:/* * We can't use the standard synchronous wrappers for file I/O; we * need to protect against async removal of the underlying spi_device. */static void spidev_complete(void *arg){ complete(arg);}该函数将使在spidev_sync函数中的wait_for_completion得以返回,从而完成了同步IO。至此,整个write系统调用的流程均以讲解完毕,在这其中也对在master和protocol中未曾给出的函数做出了一一讲解,最后,对第8章进行小结。8.9 小结从示意图中,我们可以很清除看到函数的调用过程:先调用spi设备驱动层,随后调用bitbang中间层,最后调用了master驱动层来完成数据的传输。9. read方法read方法和write方法基本差不多,关键的区别在于其发送的数据为0,而在s3c24xx_spi_txrx中断服务程序中将读取数据寄存器。下面仅仅给出函数调用示意图。在这里给出spidev_read和spidev_sync_read,方便读者进行对比。/* Read-only message with current device setup */static ssize_tspidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos){ struct spidev_data * ssize_tstatus = 0; /* chipselect only toggles at start or end of operation */ if (count & bufsiz) /*如果读取的字节数大于缓冲区的大小,则报错*/return -EMSGSIZE; spidev = filp-&private_ /*获取spidev*/ mutex_lock(&spidev-&buf_lock); /*加锁,对buffer进行互斥房屋内*/ status = spidev_sync_read(spidev, count); if (status & 0) {umissing = copy_to_user(buf, spidev-&buffer, status);if (missing == status)status = -EFAULT;elsestatus = status - } mutex_unlock(&spidev-&buf_lock);}static inline ssize_tspidev_sync_read(struct spidev_data *spidev, size_t len){struct spi_transfert = {.rx_buf= spidev-&buffer,.len= len,};struct spi_messagem;spi_message_init(&m);/*初始化message*/spi_message_add_tail(&t, &m);/*添加transfer*/return spidev_sync(spidev, &m);}10. ioctl方法这一章节中,我们将看一下SPI子系统是如何使用ioctl系统调用来实现全双工读写。10.1 spi_ioc_transfer在使用ioctl时,用户空间要使用一个数据结构来封装需要传输的数据,该结构为spi_ioc_transfe。而在write系统调用时,只是简单的从用户空间复制数据过来。该结构中的很多字段将被复制到spi_transfer结构中相应的字段。也就是说一个spi_ioc_transfer表示一个spi_transfer,用户空间可以定义多个spi_ioc_transfe,最后以数组形式传递给ioctl。下面同时给出ioctl中cmd的值。其中SPI_IOC_MASSAGE用于实现全双工IO,而其他的用于设置或者读取某个特定值。下列数据结构位于:include/linux/spi/spidev.h。/** * struct spi_ioc_transfer - describes a single SPI transfer * @tx_buf: Holds pointer to userspace buffer with transmit data, or null. * If no data is provided, zeroes are shifted out. * @rx_buf: Holds pointer to userspace buffer for receive data, or null. * @len: Length of tx and rx buffers, in bytes. * @speed_hz: Temporary override of the device's bitrate. * @bits_per_word: Temporary override of the device's wordsize. * @delay_usecs: If nonzero, how long to delay after the last bit transfer * before optionally deselecting the device before the next transfer. * @cs_change: True to deselect device before starting the next transfer. * * This structure is mapped directly to the kernel spi_ * the fields have the same meanings, except of course that the pointers * are in a different address space (and may be of different sizes in some * cases, such as 32-bit i386 userspace over a 64-bit x86_64 kernel). * Zero-initialize the structure, including currently unused fields, to * accomodate potential future updates. * * SPI_IOC_MESSAGE gives userspace the equivalent of kernel spi_sync(). * Pass it an array of related transfers, they'll execute together. * Each transfer may be half duplex (either direction) or full duplex. * * struct spi_ioc_transfer mesg[4]; * ... * status = ioctl(fd, SPI_IOC_MESSAGE(4), mesg); * * So for example one transfer might send a nine bit command (right aligned * in a 16-bit word), the next could read a block of 8-bit data before * terminating that command by temporarily
the next * could send a different nine bit command (re-selecting the chip), and the * last transfer might write some register values. */struct spi_ioc_transfer { __u64tx_ __u64rx_ __u32 __u32speed_ __u16delay_ __u8bits_per_ __u8cs_ __u32 /* If the contents of 'struct spi_ioc_transfer' ever change* incompatibly, then the ioctl number (currently 0)* ioctls with constant size fields get a bit more in the way of* error checking than ones (like this) where that field varies.** NOTE: struct layout is the same in 64bit and 32bit userspace.*/};/* not all platforms use &asm-generic/ioctl.h& or _IOC_TYPECHECK() ... */#define SPI_MSGSIZE(N) \/*SPI_MSGSIZE不能大于4KB*/((((N)*(sizeof (struct spi_ioc_transfer))) & (1 && _IOC_SIZEBITS)) \? ((N)*(sizeof (struct spi_ioc_transfer))) : 0)#define SPI_IOC_MESSAGE(N) _IOW(SPI_IOC_MAGIC, 0, char[SPI_MSGSIZE(N)])/* Read / Write of SPI mode (SPI_MODE_0..SPI_MODE_3) */#define SPI_IOC_RD_MODE_IOR(SPI_IOC_MAGIC, 1, __u8)#define SPI_IOC_WR_MODE_IOW(SPI_IOC_MAGIC, 1, __u8)/* Read / Write SPI bit justification */#define SPI_IOC_RD_LSB_FIRST_IOR(SPI_IOC_MAGIC, 2, __u8)#define SPI_IOC_WR_LSB_FIRST_IOW(SPI_IOC_MAGIC, 2, __u8)/* Read / Write SPI device word length (1..N) */#define SPI_IOC_RD_BITS_PER_WORD_IOR(SPI_IOC_MAGIC, 3, __u8)#define SPI_IOC_WR_BITS_PER_WORD_IOW(SPI_IOC_MAGIC, 3, __u8)/* Read / Write SPI device default max speed hz */#define SPI_IOC_RD_MAX_SPEED_HZ_IOR(SPI_IOC_MAGIC, 4, __u32)#define SPI_IOC_WR_MAX_SPEED_HZ_IOW(SPI_IOC_MAGIC, 4, __u32)10.2 spidev_ioctl在用户空间执行ioctl系统调用时,将会执行spidev_ioctl方法,我们来看下。下列代码位于drivers/spi/spidev.cstatic longspidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg){ interr = 0; intretval = 0; struct spidev_data * struct spi_device * u32 unsignedn_ struct spi_ioc_transfer * /* Check type and command number */ if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC) /*如果幻数不想等,则报错*/return -ENOTTY; /* Check access don't repeat below.* IOC_DIR is from the user perspective, while access_ok is* from th so they look reversed.*//*对用户空间的指针进行检查,分成读写两部分检查*/ if (_IOC_DIR(cmd) & _IOC_READ、err = !access_ok(VERIFY_WRITE, /*access_ok成功返回1*/(void __user *)arg, _IOC_SIZE(cmd)); if (err == 0 && _IOC_DIR(cmd) & _IOC_WRITE)err = !access_ok(VERIFY_READ,(void __user *)arg, _IOC_SIZE(cmd)); if (err)return -EFAULT; /* guard against device removal before, or while,* we issue this ioctl.*/ spidev = filp-&private_/*获取spidev*/ spin_lock_irq(&spidev-&spi_lock); spi = spi_dev_get(spidev-&spi);/*增加引用技术,并获取spi_device*/ spin_unlock_irq(&spidev-&spi_lock); if (spi == NULL)return -ESHUTDOWN; /* use the buffer lock here for triple duty:*- prevent I/O (from us) so calling spi_setup()*- prevent concurrent SPI_IOC_WR_* from morphing*data fields while SPI_IOC_RD_**- SPI_IOC_MESSAGE needs the buffer locked &normally&.*/ mutex_lock(&spidev-&buf_lock); /*加锁互斥体*/ switch (cmd) { /* read requests *//*读取请求*/ case SPI_IOC_RD_MODE:retval = __put_user(spi-&mode & SPI_MODE_MASK,(__u8 __user *)arg); case SPI_IOC_RD_LSB_FIRST:retval = __put_user((spi-&mode & SPI_LSB_FIRST) ?1 : 0,(__u8 __user *)arg); case SPI_IOC_RD_BITS_PER_WORD:retval = __put_user(spi-&bits_per_word, (__u8 __user *)arg); case SPI_IOC_RD_MAX_SPEED_HZ:retval = __put_user(spi-&max_speed_hz, (__u32 __user *)arg); /* write requests */ /*写请求*/ case SPI_IOC_WR_MODE:retval = __get_user(tmp, (u8 __user *)arg);if (retval == 0) {/*__get_user调用成功*/u8 save = spi-& /*保存原先的值*/if (tmp & ~SPI_MODE_MASK) {retval = -EINVAL;/*模式有错误,则跳出switch*/}tmp |= spi-&mode & ~SPI_MODE_MASK;/*这步貌似多此一举????*/spi-&mode = (u8)retval = spi_setup(spi);/*调用master-&setup方法,即s3c24xx_spi_setup*/if (retval & 0)spi-&mode = /*调用不成功,恢复参数*/elsedev_dbg(&spi-&dev, &spi mode %02x\n&, tmp);} case SPI_IOC_WR_LSB_FIRST:retval = __get_user(tmp, (__u8 __user *)arg);if (retval == 0) {u8 save = spi-&if (tmp)/*参数为正整数,设置为LSB*/spi-&mode |= SPI_LSB_FIRST;else/*参数为0,设置为非LSB*/spi-&mode &= ~SPI_LSB_FIRST;retval = spi_setup(spi);/*调用master-&setup方法,即s3c24xx_spi_setup?/if (retval & 0)spi-&mode =/*调用不成功,恢复参数*/elsedev_dbg(&spi-&dev, &%csb first\n&,tmp ? 'l' : 'm');} case SPI_IOC_WR_BITS_PER_WORD:retval = __get_user(tmp, (__u8 __user *)arg);if (retval == 0) {u8 save = spi-&bits_per_spi-&bits_per_word =retval = spi_setup(spi); /*调用master-&setup方法,即s3c24xx_spi_setup*/if (retval & 0)spi-&bits_per_word =elsedev_dbg(&spi-&dev, &%d bits per word\n&, tmp);} case SPI_IOC_WR_MAX_SPEED_HZ:retval = __get_user(tmp, (__u32 __user *)arg);if (retval == 0) {u32 save = spi-&max_speed_spi-&max_speed_hz =retval = spi_setup(spi); /*调用master-&setup方法,即s3c24xx_spi_setup*/if (retval & 0)spi-&max_speed_hz =elsedev_dbg(&spi-&dev, &%d Hz (max)\n&, tmp);} default:/* segmented and/or full-duplex I/O request */ /*全双工,接受发送数据*/if (_IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0))|| _IOC_DIR(cmd) != _IOC_WRITE) {retval = -ENOTTY;}tmp = _IOC_SIZE(cmd);/*获取参数的大小,参数为spi_ioc_transfer数组*/if ((tmp % sizeof(struct spi_ioc_transfer)) != 0) {/*检查tmp是否为后者的整数倍*/retval = -EINVAL;}n_ioc = tmp / sizeof(struct spi_ioc_transfer); /*计算共有几个spi_ioc_transfer*/if (n_ioc == 0)/* copy into scratch area */ioc = kmalloc(tmp, GFP_KERNEL);if (!ioc) {retval = -ENOMEM;}/*从用户空间拷贝spi_ioc_transfer数组,不对用户空间指针进行检查*/if (__copy_from_user(ioc, (void __user *)arg, tmp)) {kfree(ioc);retval = -EFAULT;}/* translate to spi_message, execute */retval = spidev_message(spidev, ioc, n_ioc);kfree(ioc); } mutex_unlock(&spidev-&buf_lock); spi_dev_put(spi); /*减少引用计数*/} 在函数中,首先对cmd进行了一些列的检查。随后使用switch语句来判读cmd,并执行相应的功能。cmd的第一部分为读请求,分别从寄存器读取4个参数。第二部分为写请求,分别用于修改4个参数并写入寄存器。剩余的第三部分就是全双工读写请求,这是会先计算共有多少个spi_ioc_transfer,然后分配空间,从用户空间将spi_ioc_transfer数组拷贝过来,然后将该数组和数组个数作为参数调用spidev_message。10.3 spidev_messagestatic int spidev_message(struct spidev_data *spidev,struct spi_ioc_transfer *u_xfers, unsigned n_xfers){ struct spi_ struct spi_transfer *k_ struct spi_transfer *k_ struct spi_ioc_transfer *u_ unsignedn, u8* intstatus = -EFAULT;spi_message_init(&msg);/*初始化message*/ k_xfers = kcalloc(n_xfers, sizeof(*k_tmp), GFP_KERNEL); /*分配内存,并清0*/ if (k_xfers == NULL)return -ENOMEM; /* Construct spi_message, copying any tx data to bounce buffer.* We walk the array of user-provided transfers, using each one* to initialize a kernel version of the same transfer.*/ buf = spidev-& /*所有的spi_transfer共享该buffer*/ total = 0;/*遍历spi_ioc_transfer数组,拷贝相应的参数至spi_transfer数组*/ for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_n;n--, k_tmp++, u_tmp++) {k_tmp-&len = u_tmp-&total += k_tmp-&if (total & bufsiz) {/*缓冲区长度为4096字节*/status = -EMSGSIZE;}if (u_tmp-&rx_buf) { /*需要接受收据*/k_tmp-&rx_buf =if (!access_ok(VERIFY_WRITE, (u8 __user *) /*检查指针*/(uintptr_t) u_tmp-&rx_buf,u_tmp-&len))}if (u_tmp-&tx_buf) { /*需要发送数据*/k_tmp-&tx_buf =if (copy_from_user(buf, (const u8 __user *) /*将用户空间待发送的数据拷贝至buf中*/(uintptr_t) u_tmp-&tx_buf,u_tmp-&len))}buf += k_tmp-&/*修改buf指针,指向下一个transfer的缓冲区首地址*//*复制四个参数*/k_tmp-&cs_change = !!u_tmp-&cs_k_tmp-&bits_per_word = u_tmp-&bits_per_k_tmp-&delay_usecs = u_tmp-&delay_k_tmp-&speed_hz = u_tmp-&speed_#ifdef VERBOSEdev_dbg(&spi-&dev,&xfer len %zd %s%s%s%dbits %u usec %uHz\n&,u_tmp-&len,u_tmp-&rx_buf ? &rx & : &&,u_tmp-&tx_buf ? &tx & : &&,u_tmp-&cs_change ? &cs & : &&,u_tmp-&bits_per_word ? : spi-&bits_per_word,u_tmp-&delay_usecs,u_tmp-&speed_hz ? : spi-&max_speed_hz);#endifspi_message_add_tail(k_tmp, &msg); /*添加spi_transfer到message的链表中*/ } /*spidev_sync-&spi_async-&spi_bitbang_transfer-&bitbang_work-&s3c24xx_spi_txrx*/ status = spidev_sync(spidev, &msg);if (status & 0) /* copy any rx data out of bounce buffer */ buf = spidev-& for (n = n_xfers, u_tmp = u_ n--, u_tmp++) {if (u_tmp-&rx_buf) {if (__copy_to_user((u8 __user *)(uintptr_t) u_tmp-&rx_buf, buf, /*从buf缓冲区复制数据到用户空间*/u_tmp-&len)) {status = -EFAULT;}}buf += u_tmp-& } status =done: kfree(k_xfers);} 首先,根据spi_ioc_transfer的个数,分配了同样个数的spi_transfer,把spi_ioc_transfer中的信息复制给spi_transfer,然后将spi_transfer添加到spi_message的链 表中。接着。执行了spidev_sync,这个东西似乎似曾相识,这个函数就是 8.3小结的函数。之后的过程就和前面的write、read一样了。 其实,这个函数的作用就是把所需要完成的数据传输任务转换成spi_transfer,然后添加到message的连表中。 从spidev_sync返回以后,数据传输完毕,将读取到的数据,复制到用户空间。至此,整个ioctl系统调用的过程就结束了。10.4 小结事实上,全速工io和半双工io的执行过程基本一样,只不过ioctl需要一个专用的结构体来封装传输的任务,接着将该任务转换成对应的spi_transfer,最后交给spidev_sync。11. 结束语本系列文章先从最底层的master驱动开始讲解,接着描述了高层的spi设备驱动,然后,通过系统调用接口,从上至下的讲解了整个函数的调用过程。最终,我们可以很清除看到半双工读和半双写的区别和相似之处,以及半双工IO和全双工IO的区别和相似之处。最后,希望该系列文章能帮助你了解Linux的SPI子系统。谢谢!
相关推荐:此代码为本人原创。该代码仍有不完善之处,有可能还要再次修改!仅供参考! 若有错误、疑问和意见请留言,非常感谢! 该驱动程序基于TQ2440开发板,内核2.6.30。
本系列文章对Linux设备模型中的SPI子系统进行讲解。SPI子系统的讲解将分为4个部分。
第一部分,将对SPI子系统整体进行描述,同时给出SPI的相关数据结构,最后描述SPI总线的注册。基于S3C
相关阅读排行
相关内容推荐
请激活账号
为了能正常使用评论、编辑功能及以后陆续为用户提供的其他产品,请激活账号。
您的注册邮箱:
如果您没有收到激活邮件,请注意检查垃圾箱。

我要回帖

更多关于 网贷申请很频繁的后果 的文章

 

随机推荐