专题3-串口驱动程序设计
一.tty驱动程序架构
1.概念解析:
在Linux系统中终端是一种字符设备,用tty简称各种终端设备
2.包括
(1)串口终端:
对应物理串口,一般是/dev/ttyS*,比如ttySAC0
(2)控制终端:
输出设备就是控制台终端(console),但实际上/dev/console是一个虚拟设备,他需要映射到实际的tty(物理终端)
(3)虚拟终端:
用户登录的时候就是虚拟终端。使用ctl + alt +F【1-6】可以切换到tty【1-6】,都是虚拟终端。tty0是当前中断的别名。
3.架构分析:tty子系统包含
(1)tty核心:
提供了和用户层交互的API
(2)tty线路规程(协议)
用于各种数据传送的协议制定。
(3)tty驱动
实际的物理设备驱动。
(4)结构框图
4.要想在Linux中查看某个函数被什么函数调用,就在该函数的实现体重加入dump_stack()函数。
二.初始化分析
1.驱动程序分析
(1)初始化设备
(2)打开设备
(3)读串口数据
(4)写数据到串口
2.一般驱动程序对应的文件在drivers/tty/serial下,有的是直接在drivers/serial下。对于三星的芯片来说,主要是samsung.c以及各个架构芯片对应的文件,比如s5pv210.c
3.重要数据结构
(1)调用关系
《1》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,
};
其中的 .write = tty_write,对应系统调用
《2》static ssize_t tty_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
struct inode *inode = file->f_path.dentry->d_inode;
struct tty_struct *tty = file_tty(file);
struct tty_ldisc *ld;
ssize_t ret;
if (tty_paranoia_check(tty, inode, "tty_write"))
return -EIO;
if (!tty || !tty->ops->write ||
(test_bit(TTY_IO_ERROR, &tty->flags)))
return -EIO;
/* Short term debug to catch buggy drivers */
if (tty->ops->write_room == NULL)
printk(KERN_ERR "tty driver %s lacks a write_room method.\n",
tty->driver->name);
ld = tty_ldisc_ref_wait(tty);
if (!ld->ops->write)
ret = -EIO;
else
ret = do_tty_write(ld->ops->write, tty, file, buf, count);
tty_ldisc_deref(ld);
return ret;
}
ret = do_tty_write(ld->ops->write, tty, file, buf, count);对应tty的线路规程
《3》它对应的是下面这个结构的 .write = n_tty_write,这就是线路规程调用的代码。
struct tty_ldisc_ops tty_ldisc_N_TTY = {
.magic = TTY_LDISC_MAGIC,
.name = "n_tty",
.open = n_tty_open,
.close = n_tty_close,
.flush_buffer = n_tty_flush_buffer,
.chars_in_buffer = n_tty_chars_in_buffer,
.read = n_tty_read,
.write = n_tty_write,
.ioctl = n_tty_ioctl,
.set_termios = n_tty_set_termios,
.poll = n_tty_poll,
.receive_buf = n_tty_receive_buf,
.write_wakeup = n_tty_write_wakeup
};
接下来执行的就是这个函数
《4》static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,
const unsigned char *buf, size_t nr)
{
const unsigned char *b = buf;
DECLARE_WAITQUEUE(wait, current);
int c;
ssize_t retval = 0;
/* Job control check -- must be done at start (POSIX.1 7.1.1.4). */
if (L_TOSTOP(tty) && file->f_op->write != redirected_tty_write) {
retval = tty_check_change(tty);
if (retval)
return retval;
}
/* Write out any echoed characters that are still pending */
process_echoes(tty);
add_wait_queue(&tty->write_wait, &wait);
while (1) {
set_current_state(TASK_INTERRUPTIBLE);
if (signal_pending(current)) {
retval = -ERESTARTSYS;
break;
}
if (tty_hung_up_p(file) || (tty->link && !tty->link->count)) {
retval = -EIO;
break;
}
if (O_OPOST(tty) && !(test_bit(TTY_HW_COOK_OUT, &tty->flags))) {
while (nr > 0) {
ssize_t num = process_output_block(tty, b, nr);
if (num < 0) {
if (num == -EAGAIN)
break;
retval = num;
goto break_out;
}
b += num;
nr -= num;
if (nr == 0)
break;
c = *b;
if (process_output(c, tty) < 0)
break;
b++; nr--;
}
if (tty->ops->flush_chars)
tty->ops->flush_chars(tty);
} else {
while (nr > 0) {
c = tty->ops->write(tty, b, nr);
if (c < 0) {
retval = c;
goto break_out;
}
if (!c)
break;
b += c;
nr -= c;
}
}
if (!nr)
break;
if (file->f_flags & O_NONBLOCK) {
retval = -EAGAIN;
break;
}
schedule();
}
break_out:
__set_current_state(TASK_RUNNING);
remove_wait_queue(&tty->write_wait, &wait);
if (b - buf != nr && tty->fasync)
set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
return (b - buf) ? b - buf : retval;
}
其中这条语句关键
c = tty->ops->write(tty, b, nr);
它对应的是
《5》static const struct tty_operations uart_ops = {
.open = uart_open,
.close = uart_close,
.write = uart_write,
.put_char = uart_put_char,
.flush_chars = uart_flush_chars,
.write_room = uart_write_room,
.chars_in_buffer= uart_chars_in_buffer,
.flush_buffer = uart_flush_buffer,
.ioctl = uart_ioctl,
.throttle = uart_throttle,
.unthrottle = uart_unthrottle,
.send_xchar = uart_send_xchar,
.set_termios = uart_set_termios,
.set_ldisc = uart_set_ldisc,
.stop = uart_stop,
.start = uart_start,
.hangup = uart_hangup,
.break_ctl = uart_break_ctl,
.wait_until_sent= uart_wait_until_sent,
#ifdef CONFIG_PROC_FS
.proc_fops = &uart_proc_fops,
#endif
.tiocmget = uart_tiocmget,
.tiocmset = uart_tiocmset,
.get_icount = uart_get_icount,
#ifdef CONFIG_CONSOLE_POLL
.poll_init = uart_poll_init,
.poll_get_char = uart_poll_get_char,
.poll_put_char = uart_poll_put_char,
#endif
};这里面的 .write = uart_write,
《6》接下来再在这个函数中调用驱动层的write函数完成对串口的访问。
《7》流程相当于下图
(2)数据结构层面的分析
《1》进入uart_write这个函数
static int uart_write(struct tty_struct *tty,
const unsigned char *buf, int count)
{
struct uart_state *state = tty->driver_data;
struct uart_port *port;
struct circ_buf *circ;
unsigned long flags;
int c, ret = 0;
/*
* This means you called this function _after_ the port was
* closed. No cookie for you.
*/
if (!state) {
WARN_ON(1);
return -EL3HLT;
}
port = state->uart_port;
circ = &state->xmit;
if (!circ->buf)
return 0;
spin_lock_irqsave(&port->lock, flags);
while (1) {
c = CIRC_SPACE_TO_END(circ->head, circ->tail, UART_XMIT_SIZE);
if (count < c)
c = count;
if (c <= 0)
break;
memcpy(circ->buf + circ->head, buf, c);
circ->head = (circ->head + c) & (UART_XMIT_SIZE - 1);
buf += c;
count -= c;
ret += c;
}
spin_unlock_irqrestore(&port->lock, flags);
uart_start(tty);
return ret;
}
其中有
struct uart_state *state = tty->driver_data;
struct uart_port *port;
由这句话
port = state->uart_port;
知道通过uart_state 得到uart_port 。
《2》再有这句话
uart_start(tty);跟踪以后发现 __uart_start(tty);
static void uart_start(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data;
struct uart_port *port = state->uart_port;
unsigned long flags;
spin_lock_irqsave(&port->lock, flags);
__uart_start(tty);
spin_unlock_irqrestore(&port->lock, flags);
}
继续进入 __uart_start(tty);发现有 port->ops->start_tx(port);
static void __uart_start(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data;
struct uart_port *port = state->uart_port;
if (!uart_circ_empty(&state->xmit) && state->xmit.buf &&
!tty->stopped && !tty->hw_stopped)
port->ops->start_tx(port);
}
port->ops->start_tx(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);
void (*set_termios)(struct uart_port *,
struct ktermios *new,
struct ktermios *old);
void (*pm)(struct uart_port *, unsigned int state,
unsigned int old);
unsigned int irq; /* irq number */
unsigned long irqflags; /* irq flags */
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 */
#define UPIO_DWAPB32 (8) /* DesignWare APB UART (32 bit accesses) */
unsigned int read_status_mask; /* driver specific */
unsigned int ignore_status_mask; /* driver specific */
struct uart_state *state; /* pointer to parent state */
struct uart_icount icount; /* statistics */
struct console *cons; /* struct console, if any */
#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(SUPPORT_SYSRQ)
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 irq_wake;
unsigned char unused[2];
void *private_data; /* generic platform data pointer */
};从而知道uart_port 引出了uart_ops结构体。
《3》再回到uart_write函数
由第一句话struct uart_state *state = tty->driver_data;知道state是由tty->driver_data得到的。
追踪发现是在uart_open函数里面将state的相关数据放到了tty->driver_data。
/*
* calls to uart_open are serialised by the BKL in
* fs/char_dev.c:chrdev_open()
* Note that if this fails, then uart_close() _will_ be called.
*
* In time, we want to scrap the "opening nonpresent ports"
* behaviour and implement an alternative way for setserial
* to set base addresses/ports/types. This will allow us to
* get rid of a certain amount of extra tests.
*/
static int uart_open(struct tty_struct *tty, struct file *filp)
{
struct uart_driver *drv = (struct uart_driver *)tty->driver->driver_state;
struct uart_state *state;
struct tty_port *port;
int retval, line = tty->index;
BUG_ON(!tty_locked());
pr_debug("uart_open(%d) called\n", line);
/*
* We take the semaphore inside uart_get to guarantee that we won't
* be re-entered while allocating the state structure, or while we
* request any IRQs that the driver may need. This also has the nice
* side-effect that it delays the action of uart_hangup, so we can
* guarantee that state->port.tty will always contain something
* reasonable.
*/
state = uart_get(drv, line);
if (IS_ERR(state)) {
retval = PTR_ERR(state);
goto fail;
}
port = &state->port;
/*
* Once we set tty->driver_data here, we are guaranteed that
* uart_close() will decrement the driver module use count.
* Any failures from here onwards should not touch the count.
*/
tty->driver_data = state;
state->uart_port->state = state;
tty->low_latency = (state->uart_port->flags & UPF_LOW_LATENCY) ? 1 : 0;
tty->alt_speed = 0;
tty_port_tty_set(port, tty);
/*
* If the port is in the middle of closing, bail out now.
*/
if (tty_hung_up_p(filp)) {
retval = -EAGAIN;
port->count--;
mutex_unlock(&port->mutex);
goto fail;
}
/*
* Make sure the device is in D0 state.
*/
if (port->count == 1)
uart_change_pm(state, 0);
/*
* Start up the serial port.
*/
retval = uart_startup(tty, state, 0);
/*
* If we succeeded, wait until the port is ready.
*/
mutex_unlock(&port->mutex);
if (retval == 0)
retval = tty_port_block_til_ready(port, tty, filp);
fail:
return retval;
}
state = uart_get(drv, line);进入这个函数
static struct uart_state *uart_get(struct uart_driver *drv, int line)
{
struct uart_state *state;
struct tty_port *port;
int ret = 0;
state = drv->state + line;
port = &state->port;
if (mutex_lock_interruptible(&port->mutex)) {
ret = -ERESTARTSYS;
goto err;
}
port->count++;
if (!state->uart_port || state->uart_port->flags & UPF_DEAD) {
ret = -ENXIO;
goto err_unlock;
}
return state;
err_unlock:
port->count--;
mutex_unlock(&port->mutex);
err:
return ERR_PTR(ret);
}
state = drv->state + line;
而drv是来自
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;
};
所以是uart_driver 引出的uart_state
《4》综上
uart_driver -----》uart_state-------》uart_port--------》uart_ops
而uart_ops里面有一系列的操作函数供我们使用。
《5》 UART驱动程序结构:struct uart_driver
对应一个串口驱动,就像平台驱动的platform一样
static struct uart_driver s3c24xx_uart_drv= {
.owner =THIS_MODULE,
.dev_name = "s3c2440_serial", //具体设备名称
.nr =CONFIG_SERIAL_SAMSUNG_UARTS, //定义有几个端口,一个驱动可以管理多个串口。
.cons = S3C24XX_SERIAL_CONSOLE, //console接口
.driver_name =S3C24XX_SERIAL_NAME, //串口名:ttySAC
.major =S3C24XX_SERIAL_MAJOR, //主设备号
.minor =S3C24XX_SERIAL_MINOR, //次设备号
};
《6》UART端口结构: struct uart_port
有几个串口使用就有几个端口,具体应用如下
static struct s3c24xx_uart_port
s3c24xx_serial_ports[CONFIG_SERIAL_SAMSUNG_UARTS] = {
[0] = {//串口0;
.port = {
.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[0].port.lock),
.iotype = UPIO_MEM,
.irq = IRQ_S3CUART_RX0,
.uartclk = 0,
.fifosize = 16,//定义FIFO缓存区大小
.ops = &s3c24xx_serial_ops,//串口相关操作函数
.flags = UPF_BOOT_AUTOCONF,
.line = 0,//线路
}
},
[1] = {//串口1;
.port = {
.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[1].port.lock),
.iotype = UPIO_MEM,
.irq = IRQ_S3CUART_RX1,
.uartclk = 0,
.fifosize = 16,
.ops = &s3c24xx_serial_ops,
.flags = UPF_BOOT_AUTOCONF,
.line = 1,
}
},
#if CONFIG_SERIAL_SAMSUNG_UARTS > 2
[2] = {
.port = {
.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[2].port.lock),
.iotype = UPIO_MEM,
.irq = IRQ_S3CUART_RX2,
.uartclk = 0,
.fifosize = 16,
.ops = &s3c24xx_serial_ops,
.flags = UPF_BOOT_AUTOCONF,
.line = 2,
}
},
#endif#if CONFIG_SERIAL_SAMSUNG_UARTS > 3
[3] = {
.port = {
.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[3].port.lock),
.iotype = UPIO_MEM,
.irq = IRQ_S3CUART_RX3,
.uartclk = 0,
.fifosize = 16,
.ops = &s3c24xx_serial_ops,
.flags = UPF_BOOT_AUTOCONF,
.line = 3,
}
}
#endif
};
.ops = &s3c24xx_serial_ops,//串口相关操作函数最重要
《7》UART相关操作函数结构: struct uart_ops
函数指针,一般需要我们实现的有
//一般来说,实现下面的成员函数是UART驱动的主体工作static struct uart_ops s3c24xx_serial_ops ={
.pm =s3c24xx_serial_pm, //电源管理函数
.tx_empty = s3c24xx_serial_tx_empty, //检车发送FIFO缓冲区是否空
.get_mctrl = s3c24xx_serial_get_mctrl, //是否串口流控
.set_mctrl = s3c24xx_serial_set_mctrl, //是否设置串口流控cts
.stop_tx =s3c24xx_serial_stop_tx, //停止发送
.start_tx =s3c24xx_serial_start_tx, //启动发送
.stop_rx =s3c24xx_serial_stop_rx, //停止接收
.enable_ms = s3c24xx_serial_enable_ms, //空函数
.break_ctl = s3c24xx_serial_break_ctl, //发送break信号
.startup =s3c24xx_serial_startup, //串口发送/接收,以及中断申请初始配置函数
.shutdown = s3c24xx_serial_shutdown, //关闭串口
.set_termios = s3c24xx_serial_set_termios,//串口clk,波特率,数据位等参数设置
.type = s3c24xx_serial_type, // CPU类型关于串口
.release_port =s3c24xx_serial_release_port, //释放串口
.request_port =s3c24xx_serial_request_port, //申请串口
.config_port = s3c24xx_serial_config_port, //串口的一些配置信息info
.verify_port = s3c24xx_serial_verify_port, //串口检测
.wake_peer = s3c24xx_serial_wake_peer,
};
《8》UART状态结构: struct uart_state
《9》UART信息结构: struct uart_info
用于发送和接收的时候数据缓冲存放。
4.初始化思维导图
三.打开串口
1.打开任何设备都是从dev目录下的文件入手,都有一个file_operations结构体的.open成员对应用户空间的系统调用。
2.在samsung.c中可以开始追踪调用关系
3.s3c24xx_serial_modinit====》uart_register_driver====》tty_register_driver===》cdev_init,cdev_add。到此我们可以看到cdev_init(&driver->cdev, &tty_fops);从而知道,tty或者说串口也是一个字符设备驱动,而且他的file_operations就是tty_fops。具体结构如下
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,
};
从而可见,对应用户空间的是tty_fops ---》tty_open函数。
4.在tty_io.c中找到tty_open函数。可以发现有如下语句
if (tty->ops->open)
retval = tty->ops->open(tty, filp);
二这里的ops是tty_operations类型的变量,具体的实例化就是uart_ops 具体结构如下。从而知道调用了uart_ops 的uart_open函数。
static const struct tty_operations uart_ops = { .open = uart_open,
.close = uart_close,
.write = uart_write,
.put_char = uart_put_char,
.flush_chars = uart_flush_chars,
.write_room = uart_write_room,
.chars_in_buffer= uart_chars_in_buffer,
.flush_buffer = uart_flush_buffer,
.ioctl = uart_ioctl,
.throttle = uart_throttle,
.unthrottle = uart_unthrottle,
.send_xchar = uart_send_xchar,
.set_termios = uart_set_termios,
.set_ldisc = uart_set_ldisc,
.stop = uart_stop,
.start = uart_start,
.hangup = uart_hangup,
.break_ctl = uart_break_ctl,
.wait_until_sent= uart_wait_until_sent,
#ifdef CONFIG_PROC_FS
.proc_fops = &uart_proc_fops,
#endif
.tiocmget = uart_tiocmget,
.tiocmset = uart_tiocmset,
.get_icount = uart_get_icount,
#ifdef CONFIG_CONSOLE_POLL
.poll_init = uart_poll_init,
.poll_get_char = uart_poll_get_char,
.poll_put_char = uart_poll_put_char,
#endif
};
5.在serial_core.c找到uart_open函数可以发现如下调用
retval = uart_startup(tty, state, 0);进入uart_startup之后发现如下调用
retval = uport->ops->startup(uport);
而实际上uport是uart_port类型的,他是代表一个具体的串口,他包含了针对一个串口的函数操作集。所以ops也就是串口的操作集,就是驱动要做的事情。
返回到samsung.c找到s3c24xx_serial_probe可以看到ourport = &s3c24xx_serial_ports[probe_index];所以我们的uport是从这里获取的。
进入s3c24xx_serial_ports这个数组发现.ops = &s3c24xx_serial_ops,
static struct uart_ops s3c24xx_serial_ops = {
.pm = s3c24xx_serial_pm,
.tx_empty = s3c24xx_serial_tx_empty,
.get_mctrl = s3c24xx_serial_get_mctrl,
.set_mctrl = s3c24xx_serial_set_mctrl,
.stop_tx = s3c24xx_serial_stop_tx,
.start_tx = s3c24xx_serial_start_tx,
.stop_rx = s3c24xx_serial_stop_rx,
.enable_ms = s3c24xx_serial_enable_ms,
.break_ctl = s3c24xx_serial_break_ctl,
.startup = s3c24xx_serial_startup,
.shutdown = s3c24xx_serial_shutdown,
.set_termios = s3c24xx_serial_set_termios,
.type = s3c24xx_serial_type,
.release_port = s3c24xx_serial_release_port,
.request_port = s3c24xx_serial_request_port,
.config_port = s3c24xx_serial_config_port,
.verify_port = s3c24xx_serial_verify_port,
};
可以知道操作函数集就是s3c24xx_serial_ops,就是上面说的ops的模板,而里面调用打开功能的则是 .startup = s3c24xx_serial_startup,故retval = uport->ops->startup(uport);这个语句实际上调用的就是s3c24xx_serial_ops里面的s3c24xx_serial_startup函数。
6.打开驱动函数的实现s3c24xx_serial_startup
(1)使能串口接收功能 rx_enabled(port) = 1;
(2)注册数据接收中断处理程序 ret = request_irq(ourport->rx_irq, s3c24xx_serial_rx_chars, 0, s3c24xx_serial_portname(port), ourport);
(3)使能串口发送功能 tx_enabled(port) = 1;
(4)注册数据发送中断处理程序 ret = request_irq(ourport->tx_irq, s3c24xx_serial_tx_chars, 0,
s3c24xx_serial_portname(port), ourport);
(5)如果出错就关闭
err:
s3c24xx_serial_shutdown(port);
return ret;
四.发送数据分析
1.利用对open的分析方法开始追踪调用关系,可以很快知道,用户空间write系统调用对应的是内核空间的tty_fops的tty_write函数。(字符设备层面)
2.进入该函数发现(线路规划层面)
if (!ld->ops->write) ret = -EIO;
else
ret = do_tty_write(ld->ops->write, tty, file, buf, count);
这个是借助于线路规程里面的ops->write,而它实际上又是tty_ldisc_N_TTY这个结构体变量的.write = n_tty_write,
3.在n_tty_write里我们看到c = tty->ops->write(tty, b, nr);所以这个实际上又是调用uart_ops的.write = uart_write,(核心层面)
4.uart_write===》uart_start(tty);===》__uart_start===》port->ops->start_tx(port);而start_tx来源于uart_port的ops,ops也就是struct uart_ops类型的指针,返回到samsung.c找到s3c24xx_serial_probe里面有一句话(驱动层面)
ourport = &s3c24xx_serial_ports[probe_index];找到s3c24xx_serial_ports发现
static struct s3c24xx_uart_port s3c24xx_serial_ports[CONFIG_SERIAL_SAMSUNG_UARTS] = {
[0] = {
.port = {
.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[0].port.lock),
.iotype = UPIO_MEM,
.irq = IRQ_S3CUART_RX0,
.uartclk = 0,
.fifosize = 16,
.ops = &s3c24xx_serial_ops,
.flags = UPF_BOOT_AUTOCONF,
.line = 0,
}
},
[1] = {
.port = {
.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[1].port.lock),
.iotype = UPIO_MEM,
.irq = IRQ_S3CUART_RX1,
.uartclk = 0,
.fifosize = 16,
.ops = &s3c24xx_serial_ops,
.flags = UPF_BOOT_AUTOCONF,
.line = 1,
}
},
#if CONFIG_SERIAL_SAMSUNG_UARTS > 2
[2] = {
.port = {
.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[2].port.lock),
.iotype = UPIO_MEM,
.irq = IRQ_S3CUART_RX2,
.uartclk = 0,
.fifosize = 16,
.ops = &s3c24xx_serial_ops,
.flags = UPF_BOOT_AUTOCONF,
.line = 2,
}
},
#endif
#if CONFIG_SERIAL_SAMSUNG_UARTS > 3
[3] = {
.port = {
.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[3].port.lock),
.iotype = UPIO_MEM,
.irq = IRQ_S3CUART_RX3,
.uartclk = 0,
.fifosize = 16,
.ops = &s3c24xx_serial_ops,
.flags = UPF_BOOT_AUTOCONF,
.line = 3,
}
}
#endif
};
可以看到每一个元素又有.ops = &s3c24xx_serial_ops,这就是uart_ops最终对应的结构。进入这个结构以后发现
.start_tx = s3c24xx_serial_start_tx,这就是驱动里面的函数。
5.s3c24xx_serial_start_tx的分析(实现细节)
(1)可以看到在里面实际上没有发送数据,
(2)但是有这句话enable_irq(ourport->tx_irq);这句话是激活发送数据中断,非常重要
(3)跳回到s3c24xx_serial_startup发现
ret = request_irq(ourport->tx_irq, s3c24xx_serial_tx_chars, 0,
s3c24xx_serial_portname(port), ourport);这里注册了发送中断处理程序s3c24xx_serial_tx_chars
(4)进入中断处理程序可以判断是在这里进行数据发送。串口的发送中断是在FIFO的数据量小于预先设定的值以后触发。
(5)取数据涉及到循环缓冲。在用户空间的write到驱动的具体s3c24xx_serial_start_tx函数之间要经过天通苑子系统,于是在子系统里会把从用户空间传进来的数据放到一个循环缓冲区间。然后的那个中断发生的时候,中断处理程序就会去循环缓冲区取出数据发送出去。具体的过程,回顾一下就是write--》tty_write---》n_tty_write----》uart_write---》s3c24xx_serial_start_tx---》enable_irq---》s3c24xx_serial_tx_chars是这样一个过程。于是我们要明白数据是什么时候发那个如循环缓冲区的。就是在uart_write里面放入的。可以再uart_write里看到
while (1) {
c = CIRC_SPACE_TO_END(circ->head, circ->tail, UART_XMIT_SIZE);
if (count < c)
c = count;
if (c <= 0)
break;
memcpy(circ->buf + circ->head, buf, c);
circ->head = (circ->head + c) & (UART_XMIT_SIZE - 1);
buf += c;
count -= c;
ret += c;
}
这便是放入缓冲区的过程。循环缓冲是在内核里面定义的结构体,有头有尾具体如下。
struct circ_buf {
char *buf;
int head;
int tail;
};
然后得到的数据存放是:
struct uart_state *state = tty->driver_data;
circ = &state->xmit;即存在xmit中
这个操作过程。
6.s3c24xx_serial_tx_chars函数的分析
(1)判断是否有需要发送的x_char字符(通信过程中用来表示起始和结束),如果有将其写入UTXH寄存器。
(2)判断循环缓冲是否为空以及是否不允许串口发送数据。如果两者有一个满足就调用s3c24xx_serial_stop_tx函数关闭发送中断。
(3)将循环缓冲数据取出来发送出去。利用while循环来实现,循环条件是1.发送缓冲不为空,即直到缓冲为空为止停止发送,2.且一次中断最多发送256字符,故有两个条件。
-----》当发送FIFO满的时候,退出发送。通过S3C2410_UFSTAT寄存器判断
-----》从缓冲区的尾巴取有效数据wr_regb(port, S3C2410_UTXH, xmit->buf[xmit->tail]);写到UTXH发送出去。
-----》挑战呢过循环缓冲的位置,主要是尾巴。
(4)如果循环缓冲中的数据量低于256的时候,就唤醒之前发送时阻塞的进程。这些进程阻塞是因为之前的循环缓冲已满,现在还有多余空间,可以唤醒它们继续写入数据到缓冲区。
(5)循环缓冲为空,没有数据发送,关闭发送中断,否则一直产生中断。
五.数据接收
1.应用程序调用系统调用read去读取数据,最终通过Linux内核会进入tty子系统从而进入底层驱动的read函数。但第一个相应的必然是file_operations结构体中的相关函数。
2.底层驱动的read函数从一般的file_operations结构体中匹配的,也就是 .read = tty_read,这个函数。(核心层)
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,
};
3.进入tty_read函数以后发现
if (ld->ops->read)
i = (ld->ops->read)(tty, file, buf, count);
这实际上线路规程的东西,搜查tty_ldisc_N_TTY 得到如下结构体,进一步发现时调用线路规程的 n_tty_read函数。(线路规程)。到这里以后不会再继续进行调用了。主要工作就在里面完成。
struct tty_ldisc_ops tty_ldisc_N_TTY = {
.magic = TTY_LDISC_MAGIC,
.name = "n_tty",
.open = n_tty_open,
.close = n_tty_close,
.flush_buffer = n_tty_flush_buffer,
.chars_in_buffer = n_tty_chars_in_buffer,
.read = n_tty_read,
.write = n_tty_write,
.ioctl = n_tty_ioctl,
.set_termios = n_tty_set_termios,
.poll = n_tty_poll,
.receive_buf = n_tty_receive_buf,
.write_wakeup = n_tty_write_wakeup
};
4.进入n_tty_read函数:
(1)设置当前进程为可阻塞状态,但是不会立即阻塞,而是要等到阻塞条件产生才会阻塞。
set_current_state(TASK_INTERRUPTIBLE);
(2)通过判断,如果没有数据可以读取,则利用调度让进程阻塞,等待数据到来。
if (!input_available_p(tty, 0)) {
if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) {
retval = -EIO;
break;
}
if (tty_hung_up_p(file))
break;
if (!timeout)
break;
if (file->f_flags & O_NONBLOCK) {
retval = -EAGAIN;
break;
}
if (signal_pending(current)) {
retval = -ERESTARTSYS;
break;
}
/* FIXME: does n_tty_set_room need locking ? */
n_tty_set_room(tty);
timeout = schedule_timeout(timeout);
BUG_ON(!tty->read_buf);
continue;
}
(3)如果有数据可以读取,则从缓冲区read_buf读取数据并返回给应用程序。
int uncopied;
/* The copy function takes the read lock and handles
locking internally for this case */
uncopied = copy_from_read_buf(tty, &b, &nr);
uncopied += copy_from_read_buf(tty, &b, &nr);
if (uncopied) {
retval = -EFAULT;
break;
}
(4)copy_from_read_buf的定义
static int copy_from_read_buf(struct tty_struct *tty,
unsigned char __user **b,
size_t *nr)
{
int retval;
size_t n;
unsigned long flags;
retval = 0;
spin_lock_irqsave(&tty->read_lock, flags);
n = min(tty->read_cnt, N_TTY_BUF_SIZE - tty->read_tail);
n = min(*nr, n);
spin_unlock_irqrestore(&tty->read_lock, flags);
if (n) {
retval = copy_to_user(*b, &tty->read_buf[tty->read_tail], n);
n -= retval;
tty_audit_add_data(tty, &tty->read_buf[tty->read_tail], n);
spin_lock_irqsave(&tty->read_lock, flags);
tty->read_tail = (tty->read_tail + n) & (N_TTY_BUF_SIZE-1);
tty->read_cnt -= n;
/* Turn single EOF into zero-length read */
if (L_EXTPROC(tty) && tty->icanon && n == 1) {
if (!tty->read_cnt && (*b)[n-1] == EOF_CHAR(tty))
n--;
}
spin_unlock_irqrestore(&tty->read_lock, flags);
*b += n;
*nr -= n;
}
return retval;
}
5.实际上串口驱动是和read_buf挂钩的。串口驱动一旦接受到来自己硬件的数据就会触发接收中断,紧接着就会在接收中断处理程序中将数据发送到read_buf中,然后供应用程序读取。即read_buf的数据源最终来自于串口驱动。
6.返回到samsung.c文件找到中断处理程序s3c24xx_serial_startup函数:发现有
ret = request_irq(ourport->rx_irq, s3c24xx_serial_rx_chars, 0, s3c24xx_serial_portname(port), ourport);
这个调用关系。这就是注册接收中断处理函数。
7.进入s3c24xx_serial_rx_chars:
(1)读取UFCON寄存器
(2)读取UFSTAT寄存器。立即调用s3c24xx_serial_rx_fifocnt
(3)s3c24xx_serial_rx_fifocnt函数如下
static int s3c24xx_serial_rx_fifocnt(struct s3c24xx_uart_port *ourport, unsigned long ufstat)
{
struct s3c24xx_uart_info *info = ourport->info;
if (ufstat & info->rx_fifofull)
return info->fifosize;
return (ufstat & info->rx_fifomask) >> info->rx_fifoshift;
}
通过if (ufstat & info->rx_fifofull)
return info->fifosize;
可以判断接收FIFO的数据量是多少。
(4)如果接收FIFO的数据量为0则退出处理。
(5)读取UERSTAT寄存器
(6)取出接收到的字符ch = rd_regb(port, S3C2410_URXH);通过URXH寄存器。
(7)流控处理
if (port->flags & UPF_CONS_FLOW) {
int txe = s3c24xx_serial_txempty_nofifo(port);
if (rx_enabled(port)) {
if (!txe) {
rx_enabled(port) = 0;
continue;
}
} else {
if (txe) {
ufcon |= S3C2410_UFCON_RESETRX;
wr_regl(port, S3C2410_UFCON, ufcon);
rx_enabled(port) = 1;
goto out;
}
continue;
}
}
(8)根据UERSTAT寄存器的值记录具体的错误类型。
(9)如果收到的是键盘上的sys_irq字符调用if (uart_handle_sysrq_char(port, ch))进行特殊处理。
(10)把接收到的字符送到tty_buf(串口驱动buf,不是直接到read_buf)
uart_insert_char(port, uerstat, S3C2410_UERSTAT_OVERRUN, ch, flag);
(11)一次中断最多接收64个字符,或者不足64个。当一次接收完成以后(while循环结束)调用
tty_flip_buffer_push(tty);
将串口驱动buf的数据送到线路规程的read_buf。
8.串口流控(1)流控就是流量控制,目的就是避免数据丢失,因为当一些数据发送到目的地的时候,也许目的地不能再继续接受来自外部的数据,所以需要告知数据源(发送端)暂停发送,这就是流控。
(2)软件流控就是利用程序从接收端发送一个x_off命令给发送端从而控制发送过程暂停。
(3)硬件流控:
----》CTS和RTS两个引脚。他们也是交叉相连的,CTS是被动引脚,是发送方判断是否向接收方继续发送数据的标志引脚,为高电平即表示可以发送,它不能被强行设置,随RTS的变化而变化。RTS是主动引脚,当他所在的设备还可以接受来自外部的数据的时候职位高电平,从而把与他相连的发送方的CTS引脚拉高进行数据传输。
----》自动:只要接收FIFO小于32字节,由ARM自动设置RTS为高电平,不需要人为干预。Linux主要支持自动的硬件流控。选择自动和非自动也是通过设置相应的寄存器。
----》非自动:RTS的设置可由软件执行,是否设置由接收FIFO的剩余空间决定(一般小于32),然后人为编写软件执行。
(4)Linux的自动流控主要是由UPF_CONS_FLOW设置(在s3c24xx_serial_rx_chars函数出现)
---》在s3c24xx_serial_stop_tx函数中有
if (port->flags & UPF_CONS_FLOW) s3c24xx_serial_rx_enable(port);
表明要是停止发送就要是能接收
---》在s3c24xx_serial_start_tx函数中if (port->flags & UPF_CONS_FLOW) s3c24xx_serial_rx_disable(port);
表明只要启动发送,就要停止接收
---》在s3c24xx_serial_rx_chars函数中有
if (port->flags & UPF_CONS_FLOW) { int txe = s3c24xx_serial_txempty_nofifo(port);
if (rx_enabled(port)) {
if (!txe) {
rx_enabled(port) = 0;
continue;
}
} else {
if (txe) {
ufcon |= S3C2410_UFCON_RESETRX;
wr_regl(port, S3C2410_UFCON, ufcon);
rx_enabled(port) = 1;
goto out;
}
continue;
}
}

浙公网安备 33010602011771号