Linux串口驱动程序设计
1. 在Linux系统中,终端是一类字符型设备,它包括多种类型,通常使用tty来简称各种类型的终端设备。
(1)串口终端(/dev/ttyS*):串口终端是使用计算机串口连接的终端设备。Linux把每个串行端口都看作是一个字符设备。这些串行端口所对应的设备名称是/dev/ttySAC0;/dev/ttySAC1……
(2)控制台终端(/dev/console):在Linux系统中,计算机的输出设备通常被称为控制台终端(Console),这里特指printk信息输出到的设备。/dev/console是一个虚拟的设备,它需要映射到真正的tty上,比如通过内核启动参数” console=ttySAC0”就把console映射到了串口0
(3)虚拟终端(/dev/tty*):
2. Linux tty子系统包含:tty核心,tty线路规程和tty驱动。
(1)tty核心是对整个tty设备的抽象,对用户提供统一的接口。
(2)tty线路规程是对传输数据的格式化。
(3)tty驱动则是面向tty设备的硬件驱动。
3. Linux中Uart框架:
整个 uart 框架大概的样子如上图所示。简单来分的话可以说成两层,一层是下层我们的串口驱动层,它直接与硬件相接触,我们需要填充一个 struct uart_ops 的结构体,另一层是上层 tty 层,包括 tty 核心以及线路规程,它们各自都有一个 Ops 结构,用户空通过间是 tty 注册的字符设备节点来访问,这么说来如上图所示涉及到了4个 ops 结构了,层层跳转。
4. 在 s3c2440平台,注册串口驱动的步骤是,分配一个struct uart_driver 简单填充,并调用uart_register_driver 注册到内核中去。uart_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;
};
(1) 分配uart_driver结构并简单填充(state和tty_driver将会在uart_register_driver时候赋值)
static struct uart_driver s3c24xx_uart_drv = {
.owner = THIS_MODULE,
.dev_name = "s3c2410_serial",
.nr = CONFIG_SERIAL_SAMSUNG_UARTS,
.cons = S3C24XX_SERIAL_CONSOLE,
.driver_name = S3C24XX_SERIAL_NAME,
.major = S3C24XX_SERIAL_MAJOR,
.minor = S3C24XX_SERIAL_MINOR,
};
(2) 调用uart_register_driver主要填充uart_driver的state和tty_deriver域
int uart_register_driver(struct uart_driver *drv)
{
struct tty_driver *normal = NULL;
int i, retval; BUG_ON(drv->state); /*
* Maybe we should be using a slab cache for this, especially if
* we have a large number of ports to handle.
*/
drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
retval = -ENOMEM;
if (!drv->state)
goto out; normal = alloc_tty_driver(drv->nr);
if (!normal)
goto out; drv->tty_driver = normal; normal->owner = drv->owner;
normal->driver_name = drv->driver_name;
normal->name = drv->dev_name;
normal->major = drv->major;
normal->minor_start = drv->minor;
normal->type = TTY_DRIVER_TYPE_SERIAL;
normal->subtype = SERIAL_TYPE_NORMAL;
normal->init_termios = tty_std_termios;
normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = ;
normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
normal->driver_state = drv;
tty_set_operations(normal, &uart_ops); /*
* Initialise the UART state(s).
*/
for (i = ; i < drv->nr; i++) {
struct uart_state *state = drv->state + i; state->close_delay = ; /* .5 seconds */
state->closing_wait = ; /* 30 seconds */
mutex_init(&state->mutex); tty_port_init(&state->info.port);
init_waitqueue_head(&state->info.delta_msr_wait);
tasklet_init(&state->info.tlet, uart_tasklet_action,
(unsigned long)state);
} retval = tty_register_driver(normal);
out:
if (retval < ) {
put_tty_driver(normal);
kfree(drv->state);
}
return retval;
}
5. uart_driver的state域(下层,主要是对uart口的统一封装与描述)
(1)state的类型为struct uart_state,定义如下
struct uart_state {
unsigned int close_delay; /* msec */
unsigned int closing_wait; /* msec */ #define USF_CLOSING_WAIT_INF (0)
#define USF_CLOSING_WAIT_NONE (~0U) int count;
int pm_state;
struct uart_info info;
struct uart_port *port; struct mutex mutex;
};
(2)在注册driver时,即在uart_register_driver()函数内,会根据 uart_driver->nr 来申请 nr 个 uart_state 空间,用来存放驱动所支持的串口(端口)的物理信息。在Linux内核中,每一个串口的信息由struct uart_port结构来描述,
struct uart_port {
spinlock_t lock; /* port lock */
unsigned long iobase; /* in/out[bwl] */
unsigned char __iomem *membase; /* read/write[bwl] */
unsigned int (*serial_in)(struct uart_port *, int);
void (*serial_out)(struct uart_port *, int, int);
unsigned int irq; /* irq number */
unsigned int uartclk; /* base uart clock */
unsigned int fifosize; /* tx fifo size */
unsigned char x_char; /* xon/xoff char */
unsigned char regshift; /* reg offset shift */
unsigned char iotype; /* io access style */
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 */
struct uart_icount icount; /* statistics */ struct console *cons; /* struct console, if any */
#ifdef CONFIG_SERIAL_CORE_CONSOLE
unsigned long sysrq; /* sysrq timeout */
#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_NO_TXEN_TEST ((__force upf_t) (1 << 15))
#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))
/* The exact UART type is known and should not be probed. */
#define UPF_FIXED_TYPE ((__force upf_t) (1 << 27))
#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 */
unsigned int timeout; /* character-based timeout */
unsigned int type; /* port type */
const struct uart_ops *ops;
unsigned int custom_divisor;
unsigned int line; /* port index */
resource_size_t mapbase; /* for ioremap */
struct device *dev; /* parent device */
unsigned char hub6; /* this should be in the 8250 driver */
unsigned char suspended;
unsigned char unused[];
void *private_data; /* generic platform data pointer */
};
注:这个结构体,是需要我们自己来填充的,比如我们 s3c2440 有3个串口,那么就需要填充3个 uart_port ,并且通过 uart_add_one_port 添加到 uart_driver->uart_state->uart_port 中去。当然 uart_driver 有多个 uart_state ,每个 uart_state 有一个 uart_port
(3)在 uart_port 里还有一个非常重要的成员 struct uart_ops *ops ,这个也是需要我们自己来实现的。该结构体描述了针对某一串口的具体操作方法
struct uart_ops {
unsigned int (*tx_empty)(struct uart_port *);
void (*set_mctrl)(struct uart_port *, unsigned int mctrl);
unsigned int (*get_mctrl)(struct uart_port *);
void (*stop_tx)(struct uart_port *);
void (*start_tx)(struct uart_port *);
void (*send_xchar)(struct uart_port *, char ch);
void (*stop_rx)(struct uart_port *);
void (*enable_ms)(struct uart_port *);
void (*break_ctl)(struct uart_port *, int ctl);
int (*startup)(struct uart_port *);
void (*shutdown)(struct uart_port *);
void (*flush_buffer)(struct uart_port *);
void (*set_termios)(struct uart_port *, struct ktermios *new,
struct ktermios *old);
void (*set_ldisc)(struct uart_port *);
void (*pm)(struct uart_port *, unsigned int state,
unsigned int oldstate);
int (*set_wake)(struct uart_port *, unsigned int state); /*
* Return a string describing the type of the port
*/
const char *(*type)(struct uart_port *); /*
* Release IO and memory resources used by the port.
* This includes iounmap if necessary.
*/
void (*release_port)(struct uart_port *); /*
* Request IO and memory resources used by the port.
* This includes iomapping the port if necessary.
*/
int (*request_port)(struct uart_port *);
void (*config_port)(struct uart_port *, int);
int (*verify_port)(struct uart_port *, struct serial_struct *);
int (*ioctl)(struct uart_port *, unsigned int, unsigned long);
#ifdef CONFIG_CONSOLE_POLL
void (*poll_put_char)(struct uart_port *, unsigned char);
int (*poll_get_char)(struct uart_port *);
#endif
};
6. uart_driver的tty_driver域(上层)
(1)tty_driver 是在注册过程中构建的,即在uart_register_driver()函数中构建起来的。
(2)注册过程即uart_register_driver()所做的工作
① 根据driver支持的最大设备数,申请n个 uart_state 空间,每一个 uart_state 都有一个 uart_port 。
② 分配一个 tty_driver 结构,并将drv->tty_driver 指向它
③ 对 tty_driver 进行设置,其中包括默认波特率、校验方式等
④ 初始化每一个 uart_state 的 tasklet
⑤ 注册tty_driver
注: 注册 uart_driver 实际上是注册 tty_driver,因此与用户空间打交道的工作完全交给了 tty_driver ,而且这一部分都是内核实现好的,我们不需要修改,了解一下工作原理即可。
(3)tty_dr的注册tty_register_driver()
/*
* Called by a tty driver to register itself.
*/
int tty_register_driver(struct tty_driver *driver)
{
int error;
int i;
dev_t dev;
void **p = NULL; if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM) && driver->num) {
p = kzalloc(driver->num * * sizeof(void *), GFP_KERNEL);
if (!p)
return -ENOMEM;
} if (!driver->major) {
error = alloc_chrdev_region(&dev, driver->minor_start,
driver->num, driver->name);
if (!error) {
driver->major = MAJOR(dev);
driver->minor_start = MINOR(dev);
}
} else {
dev = MKDEV(driver->major, driver->minor_start);
error = register_chrdev_region(dev, driver->num, driver->name);
}
if (error < ) {
kfree(p);
return error;
} if (p) {
driver->ttys = (struct tty_struct **)p;
driver->termios = (struct ktermios **)(p + driver->num);
} else {
driver->ttys = NULL;
driver->termios = NULL;
} cdev_init(&driver->cdev, &tty_fops);
driver->cdev.owner = driver->owner;
error = cdev_add(&driver->cdev, dev, driver->num);
if (error) {
unregister_chrdev_region(dev, driver->num);
driver->ttys = NULL;
driver->termios = NULL;
kfree(p);
return error;
} mutex_lock(&tty_mutex);
list_add(&driver->tty_drivers, &tty_drivers);
mutex_unlock(&tty_mutex); if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {
for (i = ; i < driver->num; i++)
tty_register_device(driver, i, NULL);
}
proc_tty_register_driver(driver);
driver->flags |= TTY_DRIVER_INSTALLED;
return ;
}
tty_driver注册过程
① 为线路规程和termios分配空间,并使 tty_driver 相应的成员指向它们。
② 注册字符设备,名字是 uart_driver->name 我们这里是“ttySAC”,文件操作函数集是 tty_fops。
③ 将该 uart_driver->tty_drivers 添加到全局链表 tty_drivers
④ 向 proc 文件系统添加 driver
7. 调用过程分析:tty_driver 注册了一个字符设备,我们以它的 tty_fops 入手,以 open、read、write 为例,分析用户空间如何访问到最底层的硬件操作函数
static const struct file_operations tty_fops = {
.llseek = no_llseek,
.read = tty_read,
.write = tty_write,
.poll = tty_poll,
.unlocked_ioctl = tty_ioctl,
.compat_ioctl = tty_compat_ioctl,
.open = tty_open,
.release = tty_release,
.fasync = tty_fasync,
};
(1)tty_open分析
static int tty_open(struct inode *inode, struct file *filp)
{
int ret; lock_kernel();
ret = __tty_open(inode, filp);
unlock_kernel();
return ret;
}
① tty_open的核心代码是调用函数__tty_open(),__tty_open()的简略代码如下
static int __tty_open(struct inode *inode, struct file *filp)
{
struct tty_struct *tty = NULL;
int noctty, retval;
struct tty_driver *driver;
int index; dev_t device = inode->i_rdev;
unsigned saved_flags = filp->f_flags; ... //在全局tty_drivers链表中获取Core注册的tty_driver driver = get_tty_driver(device, &index); tty = tty_init_dev(driver, index, ); // tty->ops = driver->ops; filp->private_data = tty; if (tty->ops->open)
/* 调用tty_driver->tty_foperation->open */
retval = tty->ops->open(tty, filp); return ;
}
从 tty_drivers 全局链表获取到前边我们注册进去的 tty_driver ,然后分配设置一个 struct tty_struct 的结构,最后调用 tty_struct->ops->open 函数,其实 tty_struct->ops == tty_driver->ops 。
② tty_init_dev源代码为
struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx,
int first_ok)
{
struct tty_struct *tty;
int retval; /* Check if pty master is being opened multiple times */
if (driver->subtype == PTY_TYPE_MASTER &&
(driver->flags & TTY_DRIVER_DEVPTS_MEM) && !first_ok)
return ERR_PTR(-EIO); /*
* First time open is complex, especially for PTY devices.
* This code guarantees that either everything succeeds and the
* TTY is ready for operation, or else the table slots are vacated
* and the allocated memory released. (Except that the termios
* and locked termios may be retained.)
*/ if (!try_module_get(driver->owner))
return ERR_PTR(-ENODEV); tty = alloc_tty_struct();
if (!tty)
goto fail_no_mem;
initialize_tty_struct(tty, driver, idx); retval = tty_driver_install_tty(driver, tty);
if (retval < ) {
free_tty_struct(tty);
module_put(driver->owner);
return ERR_PTR(retval);
} /*
* Structures all installed ... call the ldisc open routines.
* If we fail here just call release_tty to clean up. No need
* to decrement the use counts, as release_tty doesn't care.
*/ retval = tty_ldisc_setup(tty, tty->link);
if (retval)
goto release_mem_out;
return tty; fail_no_mem:
module_put(driver->owner);
return ERR_PTR(-ENOMEM); /* call the tty release_tty routine to clean out this slot */
release_mem_out:
if (printk_ratelimit())
printk(KERN_INFO "tty_init_dev: ldisc open failed, "
"clearing slot %d\n", idx);
release_tty(tty, idx);
return ERR_PTR(retval);
}
initialize_tty_struct():用tty_driver来初始化tty_struct结构
tty_ldisc_setup():调用线路规程中的open函数
(2)tty_open总结
① 获取tty_driver
② 根据tty_driver初始化一个tty_struct结构
* 设置 tty_struct 的线路规程为 N_TTY (不同类型的线路规程有不同的 ops)
* 初始化一个延时工作队列,唤醒时调用flush_to_ldisc ,读函数时我们需要分析它
* 初始化 tty_struct 里的两个等待队列头
* 设置 tty_struct->ops == tty_driver->ops
③ 在 tty_ldisc_setup 函数中调用到线路规程的open函数
④ 如果 tty_struct->ops 也就是 tty_driver->ops 定义了 open 函数则调用,显然是有的 uart_open
最新文章
- 有一个团队协同工具,叫Worktile
- (转载)SQL Reporting Services (Expression Examples)
- 获取设备IMEI ,手机名称,系统SDK版本号,系统版本号
- 关于CSS3线型渐变这些事儿
- vijos 1038 括号+路径 ***
- [leetcode]_Climbing Stairs
- Java教程——CMD手动编译运行失败原因(高手略过)
- C# 中使用WebClient 请求 https
- ucore lab1 bootloader学习笔记
- Android屏幕适配框架-(今日头条终极适配方案)
- [db]mysql全量迁移db
- Quartz.Net进阶之一:初识Job作业和触发器
- centos 6.X 安装nodejs v6.11.0和npm
- centos6安装自带php
- 8. String to Integer (整数的溢出)
- SVM的代码实现-python
- suoi16 随机合并试卷 (dp)
- xtrabackup 源码安装
- ubuntu 9.10 切换到root用户
- zabbix 表结构详情(基本)