设备驱动中的tty(kernel-4.7)
来源:互联网 发布:ocr软件手机版 编辑:程序博客网 时间:2024/04/29 08:54
TTY概念解析
在Linux系统中,终端是一类字符型设备,它包括多种类型,通常使用tty来简称各种类型的终端设备。
• 串口终端(/dev/ttyS*)
串口终端是使用计算机串口连接的终端设备。Linux把每个串行端口都看作是一个字符设备。这些串行端口所对应的设备名称是 /dev/ttySAC0;/dev/ttySAC1……
• 控制台终端(/dev/console)
在Linux系统中,计算机的输出设备通常被称为控制台终端(Console),这里特指printk信息输出到的设备。/dev/console是一个虚拟的设备,它需要映射到真正的tty上,比如通过内核启动参数” console=ttySAC0”就把console映射到了串口0,是一种虚拟的设备,可以和屏幕或者串口关联
• 虚拟终端(/dev/tty*)
当用户登录时,使用的是虚拟终端。使用Ctcl+Alt+[F1—F6]组合键时,我们就可以切换到tty1、tty2、tty3等上面去。tty1–tty6等称为虚拟终端,而tty0则是当前所使用虚拟终端的一个别名.
TTY架构分析
Linux tty子系统包含:tty核心,tty线路规程和tty驱动。tty核心是对整个tty设备的抽象,对用户提供统一的接口,tty线路规程是对传输数据的格式化,tty驱动则是面向tty设备的硬件驱动。
下图详细的描述了tty子系统之间的互相调用关系,即用户应用层 –> 线路规划层 –> TTY层 –> 底层驱动层 –> 物理硬件层。
串口驱动调用关系分析
串口驱动重要结构和对应的驱动调用层次:
整个 uart 框架大概的样子如上图所示,简单来分的话可以说成两层,一层是下层我们的串口驱动层,它直接与硬件相接触,我们需要填充一个 struct uart_ops
的结构体,另一层是上层 tty 层,包括 tty 核心以及线路规程,它们各自都有一个 Ops 结构,用户空通过间是 tty 注册的字符设备节点来访问,这么说来如上图所示涉及到了4个 ops 结构了,层层跳转。下面,就来分析分析它们的层次结构。
在 s3c2440 平台,它是这样来注册串口驱动的,分配一个struct uart_driver
简单填充,并调用uart_register_driver
注册到内核中去。
static struct uart_driver s3c24xx_uart_drv = { .owner = THIS_MODULE, .driver_name = "s3c2410_serial", .nr = CONFIG_SERIAL_SAMSUNG_UARTS, .cons = S3C24XX_SERIAL_CONSOLE, .dev_name = S3C24XX_SERIAL_NAME, .major = S3C24XX_SERIAL_MAJOR, .minor = S3C24XX_SERIAL_MINOR,};
uart_driver
中,只是填充了一些名字、设备号等信息,这些都是不涉及底层硬件访问的,来看一下完整的 uart_driver 结构,在include/linux/serial_core.h
中:
struct uart_driver { struct module *owner;/* 拥有该uart_driver的模块,一般为THIS_MODULE */ const char *driver_name;/* 串口驱动名,串口设备文件名以驱动名为基础 */ const char *dev_name; /* 串口设备名 */ int major; /* 主设备号 */ int minor; /* 次设备号 */ int nr; /* 该uart_driver支持的串口个数(最大) */ struct console *cons; /* 其对应的console.若该uart_driver支持serial console,否则为NULL */ /* * these are private; the low level driver should not * touch these; they should be initialised to NULL */ /* 下面这俩,它们应该被初始化为NULL */ struct uart_state *state; /* 下层,串口驱动层 */ struct tty_driver *tty_driver; /* tty相关 */ };
上边填充的结构体中,有两个成员未被赋值,对于tty_driver
代表的是上层,它会在 register_uart_driver
中的过程中赋值,而uart_state
则代表下层,uart_state
也会在register_uart_driver
的过程中分配空间,但是它里面真正设置硬件相关的东西是 uart_state->uart_port
,这个uart_port
是需要我们从其它地方调用 uart_add_one_port
来添加的。
在串口驱动层首先,需要认识这几个结构体:
/* * This is the state information which is persistent across opens. */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; //对应于一个串口设备 };
在注册 driver 时,会根据 uart_driver->nr
来申请 nr 个 uart_state
空间,用来存放驱动所支持的串口(端口)的物理信息
struct uart_port { spinlock_t lock; /* port lock */ unsigned long iobase; /* in/out[bwl] *//* io端口基地址(物理) */ unsigned char __iomem *membase;/* read/write[bwl] *//* io内存基地址(虚拟) */ 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); unsigned int (*get_mctrl)(struct uart_port *); void (*set_mctrl)(struct uart_port *, unsigned int); int (*startup)(struct uart_port *port); void (*shutdown)(struct uart_port *port); void (*throttle)(struct uart_port *port); void (*unthrottle)(struct uart_port *port); int (*handle_irq)(struct uart_port *); void (*pm)(struct uart_port *, unsigned int state, unsigned int old); void (*handle_break)(struct uart_port *); int (*rs485_config)(struct uart_port *, struct serial_rs485 *rs485); 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 *//* IO访问方式 */ unsigned char unused1;#define UPIO_PORT (SERIAL_IO_PORT) /* 8b I/O port access */#define UPIO_HUB6 (SERIAL_IO_HUB6) /* Hub6 ISA card */#define UPIO_MEM (SERIAL_IO_MEM) /* driver-specific */#define UPIO_MEM32 (SERIAL_IO_MEM32) /* 32b little endian */#define UPIO_AU (SERIAL_IO_AU) /* Au1x00 and RT288x type IO */#define UPIO_TSI (SERIAL_IO_TSI) /* Tsi108/109 type IO */#define UPIO_MEM32BE (SERIAL_IO_MEM32BE) /* 32b big endian */#define UPIO_MEM16 (SERIAL_IO_MEM16) /* 16b little endian */ 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 /* flags must be updated while holding port mutex */ upf_t flags; /* * These flags must be equivalent to the flags defined in * include/uapi/linux/tty_flags.h which are the userspace definitions * assigned from the serial_struct flags in uart_set_info() * [for bit definitions in the UPF_CHANGE_MASK] * * Bits [0..UPF_LAST_USER] are userspace defined/visible/changeable * except bit 15 (UPF_NO_TXEN_TEST) which is masked off. * The remaining bits are serial-core specific and not modifiable by * userspace. */#define UPF_FOURPORT ((__force upf_t) ASYNC_FOURPORT /* 1 */ )#define UPF_SAK ((__force upf_t) ASYNC_SAK /* 2 */ )#define UPF_SPD_HI ((__force upf_t) ASYNC_SPD_HI /* 4 */ )#define UPF_SPD_VHI ((__force upf_t) ASYNC_SPD_VHI /* 5 */ )#define UPF_SPD_CUST ((__force upf_t) ASYNC_SPD_CUST /* 0x0030 */ )#define UPF_SPD_WARP ((__force upf_t) ASYNC_SPD_WARP /* 0x1010 */ )#define UPF_SPD_MASK ((__force upf_t) ASYNC_SPD_MASK /* 0x1030 */ )#define UPF_SKIP_TEST ((__force upf_t) ASYNC_SKIP_TEST /* 6 */ )#define UPF_AUTO_IRQ ((__force upf_t) ASYNC_AUTO_IRQ /* 7 */ )#define UPF_HARDPPS_CD ((__force upf_t) ASYNC_HARDPPS_CD /* 11 */ )#define UPF_SPD_SHI ((__force upf_t) ASYNC_SPD_SHI /* 12 */ )#define UPF_LOW_LATENCY ((__force upf_t) ASYNC_LOW_LATENCY /* 13 */ )#define UPF_BUGGY_UART ((__force upf_t) ASYNC_BUGGY_UART /* 14 */ )#define UPF_NO_TXEN_TEST ((__force upf_t) (1 << 15))#define UPF_MAGIC_MULTIPLIER ((__force upf_t) ASYNC_MAGIC_MULTIPLIER /* 16 */ )/* Port has hardware-assisted h/w flow control */#define UPF_AUTO_CTS ((__force upf_t) (1 << 20))#define UPF_AUTO_RTS ((__force upf_t) (1 << 21))#define UPF_HARD_FLOW ((__force upf_t) (UPF_AUTO_CTS | UPF_AUTO_RTS))/* Port has hardware-assisted s/w flow control */#define UPF_SOFT_FLOW ((__force upf_t) (1 << 22))#define UPF_CONS_FLOW ((__force upf_t) (1 << 23))#define UPF_SHARE_IRQ ((__force upf_t) (1 << 24))#define UPF_EXAR_EFR ((__force upf_t) (1 << 25))#define UPF_BUG_THRE ((__force upf_t) (1 << 26))/* 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 0x17fff#define UPF_CHANGE_MASK ((__force upf_t) __UPF_CHANGE_MASK)#define UPF_USR_MASK ((__force upf_t) (UPF_SPD_MASK|UPF_LOW_LATENCY))#if __UPF_CHANGE_MASK > ASYNC_FLAGS#error Change mask not equivalent to userspace-visible bit defines#endif /* * Must hold termios_rwsem, port mutex and port lock to change; * can hold any one lock to read. */ upstat_t status;#define UPSTAT_CTS_ENABLE ((__force upstat_t) (1 << 0))#define UPSTAT_DCD_ENABLE ((__force upstat_t) (1 << 1))#define UPSTAT_AUTORTS ((__force upstat_t) (1 << 2))#define UPSTAT_AUTOCTS ((__force upstat_t) (1 << 3))#define UPSTAT_AUTOXOFF ((__force upstat_t) (1 << 4)) int hw_stopped; /* sw-assisted CTS flow state */ unsigned int mctrl; /* current modem ctrl settings *//* 当前的Moden 设置 */ 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 *//* 端口索引 */ unsigned int minor; resource_size_t mapbase; /* for ioremap */ /* io内存物理基地址 */ resource_size_t mapsize; 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]; struct attribute_group *attr_group; /* port specific attributes */ const struct attribute_group **tty_groups; /* all attributes (serial core use only) */ struct serial_rs485 rs485; 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
。在uart_port
里还有一个非常重要的成员struct uart_ops *ops
,这个也是需要我们自己来实现的,一般芯片厂家都写好了或者只需要稍作修改。
struct uart_port;struct serial_struct;struct device;/* * This structure describes all the operations that can be done on the * physical hardware. See Documentation/serial/driver for details. */struct uart_ops { unsigned int (*tx_empty)(struct uart_port *); /* 串口的Tx FIFO缓存是否为空 */ void (*set_mctrl)(struct uart_port *, unsigned int mctrl); /* 设置串口modem控制 */ unsigned int (*get_mctrl)(struct uart_port *); /* 获取串口modem控制 */ void (*stop_tx)(struct uart_port *); /* 禁止串口发送数据 */ void (*start_tx)(struct uart_port *);/* 使能串口发送数据 */ void (*throttle)(struct uart_port *); void (*unthrottle)(struct uart_port *); void (*send_xchar)(struct uart_port *, char ch); void (*stop_rx)(struct uart_port *); /* 禁止串口接收数据 */ void (*enable_ms)(struct uart_port *);/* 使能modem的状态信号 */ void (*break_ctl)(struct uart_port *, int ctl);/* 设置break信号 */ 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 *, struct ktermios *);/* 设置线路规程 */ void (*pm)(struct uart_port *, unsigned int state, unsigned int oldstate);/* 串口电源管理 */ /* * 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 *);/* 申请必要的IO端口/IO内存资源,必要时还可以重新映射串口端口 */ 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 int (*poll_init)(struct uart_port *); void (*poll_put_char)(struct uart_port *, unsigned char); int (*poll_get_char)(struct uart_port *);#endif};
在tty 层要从uart_register_driver
来看起了,因为tty_driver
是在注册过程中构建的,也就顺便了解了注册过程,该函数在driver/tty/serial/serail_core.c中:
/** * uart_register_driver - register a driver with the uart core layer * @drv: low level driver structure * * Register a uart driver with the core driver. We in turn register * with the tty layer, and initialise the core driver per-port state. * * We have a proc file in /proc/tty/driver which is named after the * normal driver. * * drv->port should be NULL, and the per-port structures should be * registered using uart_add_one_port after this call has succeeded. */int uart_register_driver(struct uart_driver *drv){ struct tty_driver *normal; 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. *//* 根据driver支持的最大设备数,申请n个 uart_state 空间,每一个 uart_state 都有一个uart_port */ drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL); if (!drv->state) goto out; /* tty层:分配一个 tty_driver ,并将drv->tty_driver 指向它 */ normal = alloc_tty_driver(drv->nr); if (!normal) goto out_kfree; drv->tty_driver = normal; /* 对 tty_driver 进行设置 */ 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 = 9600; 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 = 0; i < drv->nr; i++) { struct uart_state *state = drv->state + i; struct tty_port *port = &state->port; tty_port_init(port); port->ops = &uart_port_ops; } /* tty层:注册 driver->tty_driver */ retval = tty_register_driver(normal); if (retval >= 0) return retval; for (i = 0; i < drv->nr; i++) tty_port_destroy(&drv->state[i].port); put_tty_driver(normal);out_kfree: kfree(drv->state);out: return -ENOMEM;}
注册过程:
1、根据driver支持的最大设备数,申请n个uart_state
空间,每一个uart_state
都有一个 uart_port
。
2、分配一个tty_driver
,并将drv->tty_driver
指向它。
3、对tty_driver
进行设置,其中包括默认波特率、校验方式等,还有一个重要的 ops
,uart_ops
,它是tty核心与串口驱动通信的接口。
4、注册 tty_driver
。
注册 uart_driver
实际上是注册 tty_driver
,因此与用户空间打交道的工作完全交给了tty_driver
,而且这一部分都是内核实现好的,我们不需要修改,了解一下工作原理即可。
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,//当termios设置被改变时又tty核心调用 .set_ldisc = uart_set_ldisc,// 设置线路规程函数 .stop = uart_stop, .start = uart_start, .hangup = uart_hangup,// 挂起函数,当驱动挂起tty设备时调用 .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,// 获得当前tty的线路规程的设置 .tiocmset = uart_tiocmset,// 设置当前tty线路规程的设置 .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};
以上是 tty 核心的 Ops ,下面来看tty_driver
的注册。
/* * Called by a tty driver to register itself. */int tty_register_driver(struct tty_driver *driver){ int error; int i; dev_t dev; struct device *d; /* 如果没有主设备号则申请 */ 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 < 0) goto err; if (driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) { error = tty_cdev_add(driver, dev, 0, driver->num); if (error) goto err_unreg_char; } mutex_lock(&tty_mutex); /* 将该 driver->tty_drivers 添加到全局链表 tty_drivers */ list_add(&driver->tty_drivers, &tty_drivers); mutex_unlock(&tty_mutex); if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) { for (i = 0; i < driver->num; i++) { d = tty_register_device(driver, i, NULL); if (IS_ERR(d)) { error = PTR_ERR(d); goto err_unreg_devs; } } } /* proc 文件系统注册driver */ proc_tty_register_driver(driver); driver->flags |= TTY_DRIVER_INSTALLED; return 0;err_unreg_devs: for (i--; i >= 0; i--) tty_unregister_device(driver, i); mutex_lock(&tty_mutex); list_del(&driver->tty_drivers); mutex_unlock(&tty_mutex);err_unreg_char: unregister_chrdev_region(dev, driver->num);err: return error;}
调用层次:
串口数据读写函数调用流程图:
通过对串口write系统调用,进行分析,以此来判断上图是否正确, 系统之间的互相调用关系以s3c2440为例,调用图示如下:
tty驱动接口分析
tty driver
的所有操作都包含在tty_driver
中。内核即供了一个名叫alloc_tty_driver()
来分配这个tty_driver
。当然 我们也可以在自己的驱动中将它定义成一个静态的结构。对tty_driver
进行一些必要的初始化之后,调用tty_register_driver()
将其注册.
未完待续。。。。
- 设备驱动中的tty(kernel-4.7)
- 设备驱动中的device(kernel-4.7)
- 设备驱动中的device_driver(kernel-4.7)
- 设备驱动中的bus(kernel-4.7)
- 设备驱动中的kobject(kernel-4.7)
- 设备驱动中的kset(kernel-4.7)
- 设备驱动中的pinctrl(kernel-4.7)
- 设备驱动中的platform(kernel-4.7)
- 设备驱动中的class(kernel-4.7)
- 设备驱动中的i2c(kernel-4.7)
- 设备驱动中的gadget(kernel-4.7)
- 设备驱动中的mutex(kernel-4.7)
- 设备驱动中的inode(kernel-4.7)
- 设备驱动中的spin_lock(kernel-4.7)
- 设备驱动中的misc(kernel-4.7)
- 设备驱动中的iomem(kernel-4.7)
- 设备驱动中的mmc(kernel-4.7)
- 设备驱动中的pci(kernel-4.7)
- Django 返回 object 或 None
- js八皇后问题(修改)
- Java基础算法(插入、选择、冒泡、快排)
- Tomcat调用两次Filter的init()方法
- JS中选择被选中的单选框radio和jQuery选择被选中的单选框的方法以及简单的数字加减
- 设备驱动中的tty(kernel-4.7)
- c++ 求最大公约数&&最小公倍数
- Android内存泄漏的简单检查与分析方法
- 双层 not exist 嵌套
- 哈夫曼树
- Ajax学习笔记
- 16-12.15随笔
- Jquery
- npm install —— 从一个简单例子,看本地安装与全局安装的区别