Linux内核(二) [ IMX RK ] UART用户层与驱动层调用关系解析(I)

框架简介

在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操作函数,到底层具体的函数操作。
具体如何调用,后期会更新二。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bazinga bingo

您的鼓励就是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值