框架简介
在linux系统中,tty表示各种终端。终端通常都跟硬件相对应。下图为tty框架的层次结构
最上面的用户空间会有很多对底层硬件(在本文中就是WK2414 SPI扩UART设备)的操作,像read,write等。用户空间主要是通过设备文件同 tty_core交互,tty_core根据用空间操作的类型再选择跟line discipline和tty_driver也就是serial_core交互。
设置硬件的ioctl指令就直接交给serial_core处理。
Read和write操作就会交给line discipline处理。
Line discipline是线路规程的意思。正如它的名字一样,它表示的是这条终端”线程”的输入与输出规范设置,主要用来进行输入/输出数据的预处理。处理 之后,就会将数据交给serial_core,最后serial_core会调用drivers/spi/wk2xxx_spi.c的操作。
一个uart_driver通常会注册一段设备号.即在用户空间会看到uart_driver对应有多个设备节点。
例如:/dev/ttysWK0 /dev/ttysWK1 每个设备节点是对应一个具体硬件的,这样就可做到对多个硬件设备的统一管理,而每个设备文件应该对应一个uart_port,也就 是说:uart_device要和多个uart_port关系起来。并且每个uart_port对应一个circ_buf(用来接收数据),所以 uart_port必须要和这个缓存区关系起来。
结构体之间重要的关联
具体可参考:TTY-UART框架
tty_register_driver
1、将(struct tty_driver *)driver的成员cdevs与上层接口函数集连接,cdev_init(&driver->cdevs[index], &tty_fops)
uart_register_driver
1、连接UART层与TTY层 (struct tty_driver *)normal->driver_state = (struct uart_driver *)drv; (drv有各个设备uart_port不同的操作函数 空间将tty_driver的操作集统一设为了uart_ops)
2、分配(struct uart_driver *)drv-> state
uart_add_one_port
1、将uart_port和uart_driver联系起来
state->uart_port = uport // 关联state中的port和具体的port
uport->state = state; // 关联具体port中的state和uart_driver中的state
相关结构体
// 连接上层应用的接口函数
static const struct file_operations tty_fops = {
.llseek = no_llseek,
.read_iter = tty_read,
.write_iter = tty_write,
.splice_read = generic_file_splice_read,
.splice_write = iter_file_splice_write,
.poll = tty_poll,
.unlocked_ioctl = tty_ioctl,
.compat_ioctl = tty_compat_ioctl,
.open = tty_open,
.release = tty_release,
.fasync = tty_fasync,
.show_fdinfo = tty_show_fdinfo,
};
struct tty_struct {
int magic;
struct kref kref;
struct device *dev;
struct tty_driver *driver;
const struct tty_operations *ops;
int index;
/* Protects ldisc changes: Lock tty not pty */
struct ld_semaphore ldisc_sem;
struct tty_ldisc *ldisc;
struct mutex atomic_write_lock;
struct mutex legacy_mutex;
struct mutex throttle_mutex;
struct rw_semaphore termios_rwsem;
struct mutex winsize_mutex;
spinlock_t ctrl_lock;
spinlock_t flow_lock;
/* Termios values are protected by the termios rwsem */
struct ktermios termios, termios_locked;
struct termiox *termiox; /* May be NULL for unsupported */
char name[64];
struct pid *pgrp; /* Protected by ctrl lock */
struct pid *session;
unsigned long flags;
int count;
struct winsize winsize; /* winsize_mutex */
unsigned long stopped:1, /* flow_lock */
flow_stopped:1,
unused:BITS_PER_LONG - 2;
int hw_stopped;
unsigned long ctrl_status:8, /* ctrl_lock */
packet:1,
unused_ctrl:BITS_PER_LONG - 9;
unsigned int receive_room; /* Bytes free for queue */
int flow_change;
struct tty_struct *link;
struct fasync_struct *fasync;
int alt_speed; /* For magic substitution of 38400 bps */
wait_queue_head_t write_wait;
wait_queue_head_t read_wait;
struct work_struct hangup_work;
void *disc_data;
void *driver_data;
struct list_head tty_files;
#define N_TTY_BUF_SIZE 4096
int closing;
unsigned char *write_buf;
int write_cnt;
/* If the tty has a pending do_SAK, queue it here - akpm */
struct work_struct SAK_work;
struct tty_port *port;
};
struct tty_driver {
int magic; /* magic number for this structure */
struct kref kref; /* Reference management */
struct cdev *cdevs;
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 */
unsigned int num; /* number of devices allocated */
short type; /* type of tty driver */
short subtype; /* subtype of tty driver */
struct ktermios init_termios; /* Initial termios */
unsigned long flags; /* tty driver flags */
struct proc_dir_entry *proc_entry; /* /proc fs entry */
struct tty_driver *other; /* only used for the PTY driver */
/*
* Pointer to the tty data structures
*/
struct tty_struct **ttys;
struct tty_port **ports;
struct ktermios **termios;
void *driver_state;
/*
* Driver methods
*/
const struct tty_operations *ops;
struct list_head tty_drivers;
};
// 上层定义传下来的值
struct ktermios {
tcflag_t c_iflag; /* 输入模式标志位 */
tcflag_t c_oflag; /* 输出模式标志位 */
tcflag_t c_cflag; /* 控制模式标志位 */
tcflag_t c_lflag; /* local mode flags */
cc_t c_line; /* line discipline */
cc_t c_cc[NCCS]; /* control characters */
speed_t c_ispeed; /* 输入波特率 */
speed_t c_ospeed; /* 输出波特率 */
};
解析上层调用open/wirte/read调用流程
调用open函数流程
// 打开tty设备时
// drivers/tty/tty_io.c
static int tty_open(struct inode *inode, struct file *filp)
-> struct tty_struct *tty;
-> struct tty_driver *driver;
-> if (!tty)
tty = tty_open_by_driver(device, filp);
-> driver = tty_lookup_driver(device, filp, &index); // 设备根据主设备号查找驱动
-> tty = tty_driver_lookup_tty(driver, NULL, index); // 根据driver->ops->lookup定设备,若没有这个函数 tty = driver->ttys[idx];
-> tty = tty_init_dev(driver, index);
-> tty->port = driver->ports[idx];
-> retval = tty->ops->open(tty, filp); // 调用具体驱动uart_open(tty_driver结构体的操作函数集open) struct tty_struct *tty, struct file *filp
-> struct uart_driver *drv = (struct uart_driver *)tty->driver->driver_state;
-> struct uart_state *state = drv->state + line;
-> tty_port_open(&state->port, tty, filp);
-> int retval = port->ops->activate(port, tty); // port为tty_port *结构体,该结构体里操作函数activate为uart_port_activate
-> ret = uart_startup(tty, state, 0); // 打开串口
-> retval = uart_port_startup(tty, state, init_hw);
-> struct uart_port *uport = state->uart_port;
-> retval = uport->ops->startup(uport); // 调用uart_port *结构体操作集startup函数为imx_uart_startup
-> uart_change_speed(tty, state, NULL); // 串口改变速度
-> uport->ops->set_termios(uport, termios, old_termios); // 调用struct uart_port *uport结构体操作集里的set_termios,设置硬件上串口操作
//真正调用的底层函数,这部分用户自己定义,这里解析IMX 自带的代码
static void imx_set_termios(struct uart_port *port, struct ktermios *termios, struct ktermios *old)
-> if ((termios->c_cflag & CSIZE) == CS8) ...... // 判断数据位是7位还是8位的,配置要写入寄存器的值
-> if (termios->c_cflag & CRTSCTS) ...... // 判断是否有RST/CST的功能、还有485的功能看代码
-> del_timer_sync(&sport->timer); // 删除定时同步
-> baud = uart_get_baud_rate(port, termios, old, 50, port->uartclk / 16); // 获取波特率
-> upf_t flags = port->flags & UPF_SPD_MASK; // 通过标志位来获取一些特殊的波特率
-> for (try = 0; try < 2; try++) // 从终端获取两次波特率
-> baud = tty_termios_baud_rate(termios);
-> cbaud = termios->c_cflag & CBAUD; // 从c_cflag中取出表示baud的数据位
-> if (cbaud & CBAUDEX) // 判断是否为拓展波特率
-> if (cbaud < 1 || cbaud + 15 > n_baud_table) // 是否超出波特率表的范围
termios->c_cflag &= ~CBAUDEX; // 清除标志位
else
cbaud += 15; // 找到波特率位置
-> return baud_table[cbaud]; // 查表返回波特率
-> if (try == 0 && baud == 38400) // 第一次获取到波特率为38400则返回38400
-> if (baud == 0) // 如果是0 则标记挂起并返回9600
-> if (baud >= min && baud <= max) // 波特率在传入参数min/max之间就返回这个波特率 min=50 max=port->uartclk / 16
-> quot = uart_get_divisor(port, baud);
-> DIV_ROUND_CLOSEST(port->uartclk, 16 * baud); // 该函数主要进行四舍五入计算使用,内核计算
-> div = sport->port.uartclk / (baud * 16); //取整计算(这里不知道为什么要再计算一次)
-> if (div > 7) div = 7;
// 根据波特率在数组baud_bits找到对应的控制标志值,写到termios->c_cflag里
-> tty_termios_encode_baud_rate(termios, (speed_t)tdiv64, (speed_t)tdiv64);
-> if (UART_ENABLE_MS(&sport->port, termios->c_cflag))
-> imx_enable_ms(&sport->port); // 设置调制解调器控制定时器立即启动。
// 把波特率写到寄存器里
-> if (!is_imx1_uart(sport)) writel(sport->port.uartclk / div / 1000, sport->port.membase + IMX21_ONEMS);
-> imx_enable_dma(sport); start_rx_dma(sport); // 开启dma
// 例如:3/2=1.5,计算机中的整数运算结果为 3/2=1,经过DIV_ROUND_CLOSEST函数四舍五入运算后,3/2=2
#define DIV_ROUND_CLOSEST(x, divisor)( \
{ \
typeof(divisor)__divisor = divisor; \
(((x)+ ((__divisor) / 2)) / (__divisor)); \
} \
Read和write操作就会交给line discipline处理后,调用TTY操作函数,到底层具体的函数操作。
具体如何调用,后期会更新二。