第五章 Linux文件系统与设备文件
5.1 Linux文件系统与设备驱动
应用程序和VFS之间的接口是系统调用,而VFS与磁盘文件系统以及普通之间的接口是file_operations结构体成员函数。
在设备驱动程序设计中,一般而言,会关心file和inode这两个结构体
(1)file结构体
File结构体代表一个打开的文件,系统中每个打开的文件在内核空间中有一个关联的struct file
struct file {
/*
* fu_list becomes invalid after file_free is called and queued via
* fu_rcuhead for RCU freeing
*/
union {
struct list_head fu_list;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
#define f_dentry f_path.dentry
#define f_vfsmnt f_path.mnt
const struct file_operations *f_op; /*和文件关联的操作*/
atomic_long_t f_count;
unsigned int f_flags; /*文件标志,如O_RDONLY、O_NONBLOCK、O_SYNC*/
fmode_t f_mode; /*文件读/写模式*/
loff_t f_pos; /*当前读写的位置*/
struct fown_struct f_owner;
unsigned int f_uid, f_gid;
struct file_ra_state f_ra;
u64 f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data;
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
spinlock_t f_ep_lock;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
#ifdef CONFIG_DEBUG_WRITECOUNT
unsigned long f_mnt_write_state;
#endif
};
(2)inode结构体
VFS inode包括文件访问权限、属主、组、大小、生成时间、访问时间、最后修改时间等信息、他是Linux文件系统的最基本单位、也是文件系统连接任何子目录、文件的桥梁。
struct inode {
struct hlist_node i_hash;
struct list_head i_list;
struct list_head i_sb_list;
struct list_head i_dentry;
unsigned long i_ino;
atomic_t i_count;
unsigned int i_nlink;
uid_t i_uid;
gid_t i_gid;
dev_t i_rdev;
u64 i_version;
loff_t i_size;
#ifdef __NEED_I_SIZE_ORDERED
seqcount_t i_size_seqcount;
#endif
struct timespec i_atime;
struct timespec i_mtime;
struct timespec i_ctime;
unsigned int i_blkbits;
blkcnt_t i_blocks;
unsigned short i_bytes;
umode_t i_mode;
spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
struct mutex i_mutex;
struct rw_semaphore i_alloc_sem;
const struct inode_operations *i_op;
const struct file_operations *i_fop; /* former ->i_op->default_file_ops */
struct super_block *i_sb;
struct file_lock *i_flock;
struct address_space *i_mapping;
struct address_space i_data;
#ifdef CONFIG_QUOTA
struct dquot *i_dquot[MAXQUOTAS];
#endif
struct list_head i_devices;
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev;
};
int i_cindex;
__u32 i_generation;
#ifdef CONFIG_DNOTIFY
unsigned long i_dnotify_mask; /* Directory notify events */
struct dnotify_struct *i_dnotify; /* for directory notifications */
#endif
#ifdef CONFIG_INOTIFY
struct list_head inotify_watches; /* watches on this inode */
struct mutex inotify_mutex; /* protects the watches list */
#endif
unsigned long i_state;
unsigned long dirtied_when; /* jiffies of first dirtying */
unsigned int i_flags;
atomic_t i_writecount;
#ifdef CONFIG_SECURITY
void *i_security;
#endif
void *i_private; /* fs or device private pointer */
};
5.2 sysfs文件系统与Linux设备模型
Sysfs文件系统是一个虚拟文件系统,它把连接在系统上的设备和总线组织成一个分级的文件,它们可以由用户空间存取,其顶级目录包括block、device、bus、drivers、class、power和fireware。
在/sys/bus的pci子目录下,又会再分出drivers和devices目录,而devices目录中的文件对/sys/devices目录中的文件的符号连接。
Device_driver和device分别表示驱动和设备,而这两者都必须依附于一种总线,因此包含struct bus_type指针。在Linux内核中,设备和驱动是分开注册的。
第六章 字符设备驱动
6.1字符设备驱动结构
6.1.1 cdev结构体
cdev结构体描述一个字符设备,结构体如下:
获得主设备号和次设备号:
cdev_init():初始化cdev的成员,并建立cdev和file_operations之间的连接
cdev_alloc():用于动态申请一个cdev内存
cdev_add()函数和cdev_del()分别向系统添加和删除一个cdev
6.1.2 file_operation结构体
file_operation结构体中的成员函数是字符设备驱动程序设计的主体内容。这些函数实际会在应用程序进行Linux的open()、write()、read()、close()等系统调用时最终调用。
完成内核空间和用户空间内存拷贝的函数为:copy_from_user()和copy_to_user().
6.2 globalmem虚拟设备实例描述
Globalmem意味着“全局内存”,在globalmem字符设备驱动中会分配一片大笑为GLOBALMEM_SIZE的内存空间,并在驱动中提供针对该片内存的读写、控制和定位函数,以供用户空间的进程能通过Linux系统调用访问这篇内存。
第十二章 工程中的Linux设备驱动
在嵌入式系统里面,SoC系统中集成的独立的外设控制器、挂接在SoC内存空间的外设等不依附于总线。故Linux发明了一种虚拟总线,称为platform总线,相应的设备称为platform_device,而驱动称为paltform_driver。
struct platform_device {
const char * name; /*设备名*/
int id;
struct device dev;
u32 num_resources; /*设备所使用各类资源数量*/
struct resource * resource; /*资源*/
};
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*suspend_late)(struct platform_device *, pm_message_t state);
int (*resume_early)(struct platform_device *);
int (*resume)(struct platform_device *);
struct pm_ext_ops *pm;
struct device_driver driver;
};
模块加载和卸载函数仅仅通过platform_driver_register(),platform_driver_unregister()函数进行platform_driver的注册和注销,而原先的注册和注销字符设备驱动核心移交到platform_driver的probe()和remove()成员函数中。
Linux的设备驱动分层结构:
硬件的相关信息屏蔽在板文件platform_device的platform_data中。
第十四章 Linux终端设备驱动
一、终端设备驱动结构
Linux内核中的tty的层次结构如图:
tty设备发送数据流程:tty核心将用户获取的将要发给一个tty设备的数据,tty核心将数据传递给tty线路规程驱动,接着数据被传递到tty驱动,tty驱动将数据转换为可以发送给硬件的格式。
二、tty设备驱动
Driver/char/tty_io.c定义了tty设备通用的file_operations结构体并实现了接口函数tty_register_driver()用于注册tty设备,他会利用fs/char_dev.c提供的接口函数注册字符设备,与具体设备对应的tty驱动将实现tty_driver结构体中的成员函数。同时tty_io.c也提供了tty_register_ldisc()接口函数用于注册线路规程。
struct tty_driver {
int magic; /* magic number for this structure该结构体的幻数*/
struct kref kref; /* Reference management reference管理 */
struct cdev cdev;
struct module *owner;
const char *driver_name;
const char *name;
int name_base; /* offset of printed name */
int major; /* major device number主设备号*/
int minor_start; /* start of minor device number开始次设备号 */
int minor_num; /* number of *possible* devices可能的设备数量 */
int num; /* number of devices allocated被分配的设备数量 */
short type; /* type of tty driver tty驱动的类型*/
short subtype; /* subtype of tty driver tty驱动的子类 */
struct ktermios init_termios; /* Initial termios 初始的temios*/
int flags; /* tty driver flags tty驱动的标志*/
struct proc_dir_entry *proc_entry; /* /proc fs entry /proc入口*/
struct tty_driver *other; /* only used for the PTY driver 仅对PTY驱动有用*/
/*
* Pointer to the tty data structures
*/
struct tty_struct **ttys;
struct ktermios **termios;
struct ktermios **termios_locked;
void *driver_state;
/*
* Driver methods
*/
const struct tty_operations *ops;
struct list_head tty_drivers;
};
其中:driver_name是驱动名字,出现在/proc/tty和sysfs中,name是设备节点名。
Init_termios为出事线路设置,为一个termios结构体。Termios用于保存当前的线路设置,这些线路设置控制当前的波特率、数据大小、数据流控设置等。
/*temios用于保存当前的线路设置,这些线路设置当前波特率、数据大小、数据流控制*/
struct ktermios {
tcflag_t c_iflag; /* input mode flags */
tcflag_t c_oflag; /* output mode flags */
tcflag_t c_cflag; /* control mode flags */
tcflag_t c_lflag; /* local mode flags */
cc_t c_line; /* line discipline */
cc_t c_cc[NCCS]; /* control characters */
speed_t c_ispeed; /* input speed */
speed_t c_ospeed; /* output speed */
};
tty_operation结构体中有一些列的函数,主要实现了open(),close(),write(),tiocmget(),tiocmset()等函数。
三、UART设备驱动
Linux已经在serial_core.c中实现了UART设备的通用tty驱动层(姑且称其为串口核心层),这样UART驱动的主要任务变成实现serial-core.c中定义的uart_xxx接口。
1.uart_driver
Uart_driver包含了设备的驱动名、设备名、设备号等信息,它封转了tty_driver,使得递呈的UART驱动无须关心tty_driver.
struct uart_driver {
struct module *owner;
const char *driver_name; /*驱动名*/
const char *dev_name; /*设备名*/
int major; /*主设备号*/
int minor; /*次设备号*/
int nr;
struct console *cons;
/*
* these are private; the low level driver should not
* touch these; they should be initialised to NULL
*/
struct uart_state *state;
struct tty_driver *tty_driver;
};
2.uart_port
Uart_port用于描述一个UART端口的I/O端口或I/O内存地址、FIFO大小、端口等信息。
struct uart_port {
spinlock_t lock; /* port lock 端口锁*/
unsigned long iobase; /* in/out[bwl] I/O端口基地址 */
unsigned char __iomem *membase; /* read/write[bwl] I/O内存地址*/
unsigned int irq; /* irq number 终端号*/
unsigned int uartclk; /* base uart clock UART时钟*/
unsigned int fifosize; /* tx fifo size 传输的FIFO大小*/
unsigned char x_char; /* xon/xoff char xon/xoff字符 */
unsigned char regshift; /* reg offset shift 寄存器位移*/
unsigned char iotype; /* io access style I/O内存类型 */
unsigned char unused1;
#define UPIO_PORT (0)
#define UPIO_HUB6 (1)
#define UPIO_MEM (2)
#define UPIO_MEM32 (3)
#define UPIO_AU (4) /* Au1x00 type IO */
#define UPIO_TSI (5) /* Tsi108/109 type IO */
#define UPIO_DWAPB (6) /* DesignWare APB UART */
#define UPIO_RM9000 (7) /* RM9000 type IO */
unsigned int read_status_mask; /* driver specific 驱动相关的*/
unsigned int ignore_status_mask; /* driver specific 驱动相关的*/
struct uart_info *info; /* pointer to parent info 指向parent信息 */
struct uart_icount icount; /* statistics 计数*/
struct console *cons; /* struct console, if any console 结构体*/
#ifdef CONFIG_SERIAL_CORE_CONSOLE
unsigned long sysrq; /* sysrq timeout sysrq超时*/
#endif
upf_t flags;
#define UPF_FOURPORT ((__force upf_t) (1 << 1))
#define UPF_SAK ((__force upf_t) (1 << 2))
#define UPF_SPD_MASK ((__force upf_t) (0x1030))
#define UPF_SPD_HI ((__force upf_t) (0x0010))
#define UPF_SPD_VHI ((__force upf_t) (0x0020))
#define UPF_SPD_CUST ((__force upf_t) (0x0030))
#define UPF_SPD_SHI ((__force upf_t) (0x1000))
#define UPF_SPD_WARP ((__force upf_t) (0x1010))
#define UPF_SKIP_TEST ((__force upf_t) (1 << 6))
#define UPF_AUTO_IRQ ((__force upf_t) (1 << 7))
#define UPF_HARDPPS_CD ((__force upf_t) (1 << 11))
#define UPF_LOW_LATENCY ((__force upf_t) (1 << 13))
#define UPF_BUGGY_UART ((__force upf_t) (1 << 14))
#define UPF_MAGIC_MULTIPLIER ((__force upf_t) (1 << 16))
#define UPF_CONS_FLOW ((__force upf_t) (1 << 23))
#define UPF_SHARE_IRQ ((__force upf_t) (1 << 24))
#define UPF_BOOT_AUTOCONF ((__force upf_t) (1 << 28))
#define UPF_FIXED_PORT ((__force upf_t) (1 << 29))
#define UPF_DEAD ((__force upf_t) (1 << 30))
#define UPF_IOREMAP ((__force upf_t) (1 << 31))
#define UPF_CHANGE_MASK ((__force upf_t) (0x17fff))
#define UPF_USR_MASK ((__force upf_t) (UPF_SPD_MASK|UPF_LOW_LATENCY))
unsigned int mctrl; /* current modem ctrl settings 目前的modem控制设置*/
unsigned int timeout; /* character-based timeout 基于字符的超时*/
unsigned int type; /* port type 端口类型*/
const struct uart_ops *ops; /*UART操作集*/
unsigned int custom_divisor;
unsigned int line; /* port index 端口索引*/
resource_size_t mapbase; /* for ioremap ioremap后基地址*/
struct device *dev; /* parent device parament设备 */
unsigned char hub6; /* this should be in the 8250 driver */
unsigned char suspended;
unsigned char unused[2];
void *private_data; /* generic platform data pointer */
};
3、uart_ops
Uart_ops定义了针对UART的一些列操作,包括发送、接收及线路设置等。
在serial_core.c中定义了tty_operations的实例,包含uart_open(),uart_close()等。这些函数会借助uart_ops结构体中成员函数来完成具体的操作。例如,tty_operations的uart_send_xchar()成员函数利用uart_ops中的start_tx()、send_xchar()成员函数的实例。
因此,一个串口驱动要完成的主要工作如下:
(1)定义uart_driver、uart_ops、uart_port等结构体的实例并在适当的地方更具具体的硬件驱动情况初始化他们,当然具体设备xxx的驱动可以将这些结构体套在新定义的xxx_uart_driver、xxx_uart_ops、xxx_uart_port之内。
(2)在模块初始化调用uart_register()和uart_add_one_port()以注册UART驱动并添加端口,在模块卸载时调用uart_unregister_driver()和uart_remove_one_port()以注销UART驱动以移除端口。
(3)根据具体硬件的datasheetshixianuart_ops中的成员函数,这些函数的实现成为UART驱动的主体工作。
S3C6410串口UART驱动:samsung.c的模块加载和卸载函数中,通过uart_register_driver()、uart_unregister_driver()注册和注销了s3c24xx_uart_drv,s3c6400.c是一个platform驱动。