Linux驱动之tty子系统(简要分析以及驱动实现)

Linux驱动之tty子系统

深入linux源码探究!

1. TTY 子系统概述

在 Linux 内核中,TTY (Teletype) 子系统是 用户空间与字符设备交互的桥梁,它最早用于模拟电传打字机(Teletype),但如今主要用于 终端、串口、伪终端(PTY)等字符设备的管理。

TTY 子系统负责:

  • 字符设备驱动(如串口、虚拟终端等)的管理。
  • 终端 I/O 控制(如 stty 命令)。
  • 行规处理(输入缓冲、回显、信号处理等)。
  • 用户进程与设备的连接(如 bash 终端使用 /dev/tty)。

2. TTY 子系统架构

Linux TTY 子系统由 四层 组成:

  1. 用户空间 (User Space)
  • shell, minicom, cat /dev/ttyS0, ioctl()
  1. TTY 核心层 (TTY Core Layer)
  • tty_operations, tty_ldisc, tty_driver
  • TTY 核心层包含线路规程
  1. 线路规程 (Line Discipline)
  • N_TTY (标准终端), N_SLIP, N_PPP
  1. 低级驱动 (Low-level Drivers)
  • 串口 (serial), 虚拟终端 (virtual tty), 伪终端 (pty)

在这里插入图片描述

(1)用户空间

用户进程可以通过 /dev/ttyX 访问 TTY 设备,常见的方式包括:

  • 文件操作:open("/dev/ttyS0")read/write
  • IOCTL 控制:如 tcsetattr() 设置波特率、设置线路规程等。
  • 终端模拟器:如 minicomscreentmux

(2)TTY 核心层

TTY 核心层提供 TTY 设备的抽象接口,包括:

  • struct tty_driver:描述 TTY 设备的驱动信息。
  • struct tty_operations:TTY 设备操作函数,如 open()write()read()
  • struct tty_port:管理 TTY 端口,包括缓冲区、引用计数。
  • struct tty_ldisc:描述 ldisc线路规程(一般不增删、tty核心层已经做好不同线路规程的结构体实体)

(3)线路规程(Line Discipline)

  1. TTY 设备的输入数据可以通过不同的 线路规程 进行处理,在 Linux 中,有多个常见的线路规程。最常用的有:
  • N_TTY(默认)(TTY_LINE_DISC_TERMINAL):标准的文本终端处理规程。用于普通的字符终端设备,比如虚拟终端(tty、ttyS 等)。它负责处理普通的字符回显、行编辑、字符映射等。

  • N_PTY(TTY_LINE_DISC_PTY):伪终端处理规程。用于创建伪终端设备(如 tty 或 pty),用于实现终端模拟(如终端仿真器)。N_PTY 允许一个进程模拟另一个进程的终端行为。

  • N_SERIAL(TTY_LINE_DISC_SERIAL):用于串口通信的线路规程。该规程处理与串口设备(如 8250 串口)之间的通信,包括流控制、数据转换等。

  • N_UUCP:用于 UUCP(Unix-to-Unix Copy Protocol)通信的线路规程。它支持特定的通信协议,通常用于较早的拨号通信。

  • N_SG:用于高性能串口设备的线路规程。

  1. 一些常见的用于设置线路规划模式的 IOCTL 命令包括:
  • TIOCSETD:设置 TTY 设备的线路规划。
  • TIOCSERIAL:设置串口的相关属性(例如波特率、停止位等)。
  • TCSETA、TCSETAW、TCSETAF 等:设置终端的 termios 配置。
  1. 线路规程模式是在用户层指定tcsetattr() -> ioctl() -> 按照传递进来的CMD参数来创建不同的线路规程模式,并且添加到对应的tty_struct结构体中

(4)低级驱动

低级驱动负责具体设备的实现,包括:

  • 串口驱动:drivers/tty/serial/,如 8250 驱动。
  • 虚拟终端:drivers/tty/vt/,如 tty0tty1
  • 伪终端(PTY):drivers/tty/pty.c,用于 sshtmux 等。

3. TTY 关键数据结构

(1)TTY 驱动结构体 tty_driver

  • tty_driver**ttys**ports**cdevs都是在tty_alloc_driver函数中分配得的内存空间(分别是申请指向*tty_struct*port*cdev的数组,但是具体的porttty_structcdev并没有在这里分配)

定义在 include/linux/tty_driver.h

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;    // 这个地方就是tty驱动层抽象底下不同设备的驱动的指针变量

	/*
	 * Driver methods
	 */

	const struct tty_operations *ops;
	struct list_head tty_drivers;
}
  1. 作用:
  • 注册 TTY 设备:使用 tty_register_driver()
  • 管理多个 TTY 端口:如 /dev/ttyS0, /dev/ttyS1
  • 存储具体设备的相关数据指针
    • void *driver_state;这个地方就是tty驱动层抽象底下不同设备的驱动的指针变量。
    • 比如在uart的驱动中就用driver_state这成员变量指向&uart_driver用来存储uart串口的信息,便于tty_operation中的操作集访问。
  • 一个tty_driver会对应多个tty_port
  • 在设备打开后,几乎操作的都是tty_struct来获取所有与进程和tty_port相关信息
  1. uart具体设备的一些结构体
  • struct 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;

	struct uart_state	*state;
	struct tty_driver	*tty_driver;
};
  • struct uart_state
struct uart_state {
	struct tty_port		port;

	enum uart_pm_state	pm_state;
	struct circ_buf		xmit;

	atomic_t		refcount;
	wait_queue_head_t	remove_wait;
	struct uart_port	*uart_port;
};
  • struct uart_port
struct uart_port {
    ...
    struct uart_state       *state; 
    ...
    const struct uart_ops   *ops;
    ...
    unsigned int            line;     /* port index */
    ...
};

(2)TTY 设备操作 tty_operations

  1. tty_operations是tty核心层暴露给具体设备操作集接口,注册申请tty设备需要实现tty_operations结构体中的函数集。
  • 比如uart驱动中就实现了tty_operations操作集
  • 如果需要注册一个tty设备就得实现tty_operations操作集
  1. 定义
    定义在 include/linux/tty_driver.h
struct tty_operations {
    int (*open)(struct tty_struct *tty, struct file *filp);
    void (*close)(struct tty_struct *tty, struct file *filp);
    int (*write)(struct tty_struct *tty, const unsigned char *buf, int count);
    ...
};
  1. tty_operations操作集的具体用到的地方在本文第4节《tty框架的write/read流程》中的图片中有标注。

  2. 常见操作:

  • open():打开 TTY 设备。
  • close():关闭 TTY 设备。
  • write():写入数据到 TTY 设备。

(3)TTY 端口 tty_port

  1. TTY 端口管理,如缓冲区、状态:

  2. tty_port 需要手动分配和初始化,并且tty_port初始化后必须挂在到tty_driverports[i]中,或者挂在到tty_struct中的port字段,否则会引起crash the kernel,(在uart驱动中,已经将tty_port添加tty_struct中的port字段中了),

  3. 或者在需要操作tty_port的地方能够正确找到创建的tty_port,比如在tty_operations的open中就需要 tty_port_open()来传入tty_port*

  4. tty_port_install()中可以将tty_struct中的port字段和tty_port关联

  5. 重要的是tty_struct中的port字段需要被填充struct port*,如下(在tty_init_dev()中):

if (!tty->port)
		tty->port = driver->ports[idx];
struct tty_port {
	struct tty_bufhead	buf;		/* Locked internally */
	struct tty_struct	*tty;		/* Back pointer */
	struct tty_struct	*itty;		/* internal back ptr */
	const struct tty_port_operations *ops;	/* Port operations */
	const struct tty_port_client_operations *client_ops; /* Port client operations */
	spinlock_t		lock;		/* Lock protecting tty field */
	int			blocked_open;	/* Waiting to open */
	int			count;		/* Usage count */
	wait_queue_head_t	open_wait;	/* Open waiters */
	wait_queue_head_t	delta_msr_wait;	/* Modem status change */
	unsigned long		flags;		/* User TTY flags ASYNC_ */
	unsigned long		iflags;		/* Internal flags TTY_PORT_ */
	unsigned char		console:1,	/* port is a console */
				low_latency:1;	/* optional: tune for latency */
	struct mutex		mutex;		/* Locking */
	struct mutex		buf_mutex;	/* Buffer alloc lock */
	unsigned char		*xmit_buf;	/* Optional buffer */
	unsigned int		close_delay;	/* Close port delay */
	unsigned int		closing_wait;	/* Delay for output */
	int			drain_delay;	/* Set to zero if no pure time
						   based drain is needed else
						   set to size of fifo */
	struct kref		kref;		/* Ref counter */
	void 			*client_data;
};

(4)TTY 结构体 tty_struct

  • tty_struct是整个tty子系统管理的核心结构体,负责将进程、tty_drivertty_port联系起来。

  • tty_struct由 TTY 核心在打开 TTY 设备时在tty_init_dev中自动创建tty_struct,并添加到tty_driver*ttys[tty->index]中。

  • tty_port->tty 并不会存储所有 tty_struct,它只是一个指针,指向最近一次成功打开的 tty_struct

4. tty框架的write/read流程

在这里插入图片描述

上面的write/read是在linux源码一步一步跳转查看的。具体流程应该差不多。

5. 简单虚拟tty设备框架

  • 这里的tty_port是直接创建初始化,并挂在在tty_driver->ports[i]中,并没有在其他install(tty_operations)的接口上把tty_port挂载到tty->port上。
  • 也可以实现install接口来实现把tty_port挂载到tty->port上。
  1. 代码说明:
  • 这里代码会生成/dev/ttyPCIE0/dev/ttyPCIE1设备。
  • 向其中一个设备写入数据会写入到另一个设备中,可以从另一个设备读取。
  • 可以用minicom工具打开测试
    • sudo minicom -D /dev/ttyPCIE0
    • sudo minicom -D /dev/ttyPCIE1
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/tty.h>
#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/serial.h>
#include <linux/serial_core.h>
#include <linux/uaccess.h>
#include <linux/mutex.h>

#define TTY_MAJOR_AUTO 0
#define VIRTUAL_TTY_MINORS 2
#define DRIVER_NAME "gps_pcie_tty"

static struct tty_driver *gps_pcie_tty_driver;
static struct tty_port *gps_pcie_tty_ports[VIRTUAL_TTY_MINORS] = {NULL, NULL};


static const struct tty_port_operations gps_pcie_tty_port_ops = {};

static int gps_pcie_tty_open(struct tty_struct *tty, struct file *filp)
{
    int port_num = tty->index;

    if (port_num >= VIRTUAL_TTY_MINORS)
        return -ENODEV;

    // 设置 tty->port
    tty->port = gps_pcie_tty_ports[port_num];

    // 打开端口
    return tty_port_open(tty->port, tty, filp);
}

static void gps_pcie_tty_close(struct tty_struct *tty, struct file *filp)
{
    tty_port_close(tty->port, tty, filp);
}

static int gps_pcie_tty_write(struct tty_struct *tty, const unsigned char *buf, int count)
{
    int port_num = tty->index;
    
    int other_port_num = (port_num == 0) ? 1 : 0;
    
    struct tty_port *other_port = gps_pcie_tty_ports[other_port_num];
    struct tty_struct *other_tty = tty_port_tty_get(other_port);
    // 另一个tty设备
    if (other_tty) {
        // 向上递交数据
        tty_insert_flip_string(other_port, buf, count);
        tty_flip_buffer_push(other_port);
        tty_kref_put(other_tty);
    }

    return count;
}

static const struct tty_operations gps_pcie_tty_ops = {
    .open = gps_pcie_tty_open,
    .close = gps_pcie_tty_close,
    .write = gps_pcie_tty_write,
};

static int __init gps_pcie_tty_init(void)
{
    int ret;
    int i;

    gps_pcie_tty_driver = tty_alloc_driver(VIRTUAL_TTY_MINORS, TTY_DRIVER_REAL_RAW);
    if (IS_ERR(gps_pcie_tty_driver))
        return PTR_ERR(gps_pcie_tty_driver);

    gps_pcie_tty_driver->owner = THIS_MODULE;
    gps_pcie_tty_driver->driver_name = DRIVER_NAME;
    gps_pcie_tty_driver->name = "ttyPCIE";
    gps_pcie_tty_driver->major = TTY_MAJOR_AUTO;
    gps_pcie_tty_driver->minor_start = 0;
    gps_pcie_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
    gps_pcie_tty_driver->subtype = SERIAL_TYPE_NORMAL;
    gps_pcie_tty_driver->init_termios = tty_std_termios;
    gps_pcie_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
    tty_set_operations(gps_pcie_tty_driver, &gps_pcie_tty_ops);

    for (i = 0; i < VIRTUAL_TTY_MINORS; i++) {
        gps_pcie_tty_ports[i] = kzalloc(sizeof(struct tty_port), GFP_KERNEL);
        if (!gps_pcie_tty_ports[i]) {
            ret = -ENOMEM;
            goto err_free_ports;
        }
        tty_port_init(gps_pcie_tty_ports[i]);
        gps_pcie_tty_ports[i]->ops = &gps_pcie_tty_port_ops;
        gps_pcie_tty_driver->ports[i] = gps_pcie_tty_ports[i];
        }
    ret = tty_register_driver(gps_pcie_tty_driver);
    if (ret) {
        goto err_free_ports;
    }
    pr_info("gps_pcie_tty loaded\n");
    return 0;

err_free_ports:
    for (i = 0; i < VIRTUAL_TTY_MINORS; i++) {
        if (gps_pcie_tty_ports[i])
        {
            kfree(gps_pcie_tty_ports[i]);
            gps_pcie_tty_ports[i] = NULL;
        }
    }
    if(gps_pcie_tty_driver)
    {
        tty_driver_kref_put(gps_pcie_tty_driver);
    }
    return ret;
}

static void __exit gps_pcie_tty_exit(void)
{
    int i;

    tty_unregister_driver(gps_pcie_tty_driver);
    for (i = 0; i < VIRTUAL_TTY_MINORS; i++) {
        if (gps_pcie_tty_ports[i] != NULL)
        {
            tty_port_tty_hangup(gps_pcie_tty_ports[i], false);
            tty_port_destroy(gps_pcie_tty_ports[i]);
            kfree(gps_pcie_tty_ports[i]);
            gps_pcie_tty_ports[i] = NULL;
        }
    }

    if(gps_pcie_tty_driver)
    {
        tty_driver_kref_put(gps_pcie_tty_driver);
    }
    gps_pcie_tty_driver = NULL;
    pr_info("gps_pcie_tty driver unloaded\n");
}

module_init(gps_pcie_tty_init);
module_exit(gps_pcie_tty_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("One Code");
MODULE_DESCRIPTION("One-Code PCIE TTY DRIVER");
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值