精华内容
下载资源
问答
  • 串行设备驱动程序

    2016-07-01 10:48:40
    1. 关注UART或其他底层串行硬件特征的底层驱动程序 2. 与底层驱动程序接口的TTY驱动程序层,TTY驱动程序将上层驱动程序与各色的硬件进行隔离 3. 加工用于和TTY(Teletype)驱动程序交换数据的线路规程。线路规程...

    1. 关注UART或其他底层串行硬件特征的底层驱动程序

    2. 与底层驱动程序接口的TTY驱动程序层,TTY驱动程序将上层驱动程序与各色的硬件进行隔离

    3. 加工用于和TTY(Teletype)驱动程序交换数据的线路规程。线路规程勾勒串行层的行为。有助于复用底层的代码来支持不同的技术。


    TTY(IO核心)  n_tty(/dev/ttySX(串行子系统终端) n_irda(/dev/ircommX)红外  N_PPP(ppp0) 拨号网络 --- 线路规程


    TTY驱动程序


    底层驱动程序


    串行端口(底层硬件)


    linux tty pty pts 

    概念

     

    区别

     

    基本概念:

     

     

    1. tty(

    终端设备的统称

    ): 

     

     

     

     

    tty

    一词源于

    Teletypes

    ,或者

    teletypewriters

    ,原来指的是电传打字机,是通过串行线用

    打印机键盘通过阅读和发送信息的东西,

    后来这东西被键盘与显示器取代,

    所以现在叫终端

    比较合适。

     

     

     

     

     

    终端是一种字符型设备,它有多种类型,通常使用

    tty

    来简称各种类型的终端设备。

     

     

    2. pty

    (虚拟终端

    ): 

     

     

     

     

    但是如果我们远程

    telnet

    到主机或使用

    xterm

    时不也需要一个终端交互么?是的,这就

    是虚拟终端

    pty(pseudo-tty) 

     

    3. pts/ptmx(pts/ptmx

    结合使用,进

    而实

    pty): 

    pts(pseudo-terminal 

    slave)

    pty

    方法

    ,与

    ptmx(pseudo-terminal 

    master)

    合使用

    pty

     

    linux tty pty pts 概念 区别 
    基本概念:  
    1. tty(终端设备的统称): 


        tty一词源于Teletypes,或者teletypewriters,原来指的是电传打字机,是通过串行线用打印机键盘通过阅读和发送信息的东西,后来这东西被键盘与显示器取代,所以现在叫终端比较合适。 
        终端是一种字符型设备,它有多种类型,通常使用tty来简称各种类型的终端设备。  

    2. pty(虚拟终端): 
       

       但是如果我们远程telnet到主机或使用xterm时不也需要一个终端交互么?是的,这就是虚拟终端pty(pseudo-tty)  

    3. pts/ptmx(pts/ptmx结合使用,进而实现pty): 

       pts(pseudo-terminal slave)是pty的实现方法,与ptmx(pseudo-terminal master)配合使用实现pty。 


    一 :前言 
    终端设备 
    在Linux系统中,终端是一种字符型设备,它有多种类型,通常使用tty来简称各种类型的终端设备。tty是Teletype的缩写,Teletype是最早出现的一种终端设备,很像电传打字机,是由Teletype公司生产的。Linux中包含如下几类终端设备: 

    1.串行端口终端(/dev/ttySn) 
    串行端口终端(Serial Port Terminal)是使用计算机串行端口连接的终端设备。

     2.伪终端(/dev/pty/) 
    伪终端(Pseudo Terminal)是成对的逻辑终端设备 

    3.控制台终端(/dev/ttyn, /dev/console)




    Tty_core:  Tty核心层 


    Line discipline : 是线路规程的意思(链路层)。正如它的名字一样,它表示的是这条
    终端”线程”的输入与输出规范设置.主要用来进行输入/输出数据的预处理 

    Tty_driver: Tty_driver就是终端对应的驱动了。它将字符转换成终端可以理解的字串.将其传给终端设备。

    展开全文
  • 一、相关简介  1、UART(串行异步shou'fa)

    一、相关简介

     1、UART(串行异步收发器)常用来实现串行通信。在PC兼容机硬件上,UART是super I/O芯片组的一部分。(图6-1  PC串行端口连接图)


     2、尽管RS-232通信信道是常见的串行硬件,内核的子系统还是用通用化的方式组织在一起,以便服务不同的用户。



    二、层次架构

     1、内核开发者用如下的构建模块去构造分层次的串行架构:

       1)、关注UART或其他底层串行硬件特征的底层驱动程序。

       2)、和底层驱动程序接口的tty驱动程序层。tty驱动程序将上层驱动程序和形形色色的硬件进行了隔离。

       3)、加工用于和tty驱动程序交换数据的线路规程。线路规程勾勒串行层的行为,有助于复用底层的代码来支持不同的技术。


     2、串行子系统层间链接关系图(P119  图6-2)


     3、串行子系统与内核源文件的映射关系(P120  图6-3)



    三、UART驱动程序

     1、UART驱动程序围绕三个关键的数据结构展开,这三个关键的数据结构哦都定义在include/linux/serial_core.h中。

       1)、特定UART相关的驱动程序结构,struct_uart_driver:

           struct_uart_driver {

               struct module  *ower;

               const  char  *driver_name;

               const  char  *dev_name;

                /*  .  .  .  */

                int  major;

                int  minor;

                /*  .  .  .  */

                struct  tty_driver  *tty_driver;

           };

       2)、struct  uart_port。UART驱动程序拥有的每个端口都存在uart_port结构的一个实例。

           struct  uart_port {

              spinlock_t  lock;

              unsigned  int  iobase;

              unsigned  char  __iomem  *membase;

              unisgend  int  irq;

              unisgned  int  uartclk;

              unsigned  char  fifosize;

              unsigned  char  x_char;

              /*  .  .  .  */

           };

       3)、struct  uart_ops。这个结构是每个UART驱动程序必须支持的物理硬件上可完成的操作的入口函数的超集。

           struct  uart_ops {

               uint  (*tx_empty)(struct  uart_port  *);

               void  (*set_mctrl)(struct  uart_port  *,  unsigned  int  mctrl);

               uint  (*get_mctrl)(struct  uart_port  *);

               void  (*stop_tx)(struct  uart_port  *);

               void  (*start_tx)(struct  uart_port  *);

               /*  .  .  .  */

               void  (*shutdown)(struct  uart_port  *);

               void  (*set_termios)(struct  uart_port  *,  struct  termios  *new,  struct  termios  *old);

               /*  .  .  .  */

               void  (*config_port)(struct  uart_driver  *,  int);


     2、UART驱动程序为了将自身和内核联系起来,必须完成两个重要的步骤。

       1)、通过调用uart_register_driver(struct  uart_driver  *);向串行核心注册。

       2)、调用uart_add_one_port(struct  uart_driver  *,  struct  uart_port  *)注册其支持的每个接口。


     3、设备实例:手机

       1)、Linux手机上的USB_UART端口(P123  图6-5)。

       2)、USB_UART上的寄存器(P123  表6-1)。

       3)、平台由平台设备和平台驱动程序组成。

       4)、代码清单6-1:Linux手机的USB_UART驱动程序。


     4、RS-485



    四、TTY驱动程序

     1、tty驱动程序的核心结构和注册函数。有三个结构非常重要。

       1)、定义于include/linux/tty.h中的struct  tty_struct。此结构包含了和打开的tty相关的所欲哦状态信息。其结构成员众多下面列出一些重要的成员:

          struct  tty_driver {

              int  magic;

              struct  tty_driver  *driver;

              struct  tty_ldisc  ldisc;

              /*  .  .  .   */

              struct  tty_filp_buffer  flip;

              /*  .  .  .   */

              wait_queue_head_t  write_wait;

              wait_queue_head_t  read_wait;

              /*  .  .  .   */

           };

       2)、tty_struct中内嵌的struct  tty_flip_buffer或flip缓冲区。这是数据收集和处理机制的中枢:

          struct  tty_flip_buffer {

             /*  .  .  .  */

             struct  semaphore  pty_sem;

             char  *char_buf_ptr;

             /*  .  .  .  */

             unsigned  char  char_buf[2*TTY_FLIPBUF_SIZE];

             /*  .  .  .  */

          };

          在最近的内核中,struct  tty_flip_buffer结构体有些改动,目前由缓冲区头部(tty_buhead)和缓冲区链表(tty_buffer)组成:

          struct_tty_bufhead {

             /*  .  .  .  */

             struct  semaphore  pty_sem;

             struct  tty_buffer  *head,  tail,  free;

              /*  .  .  .  */

           };

           struct  tty_buffer {

              struct  tty_buffer  *next;

              struct  *char_buf_ptr;

              /*  .  .  .  */

              unsigned  long  data[0];

           };

       3)、定义在include/linux/tty_driver.h文件中的struct  tty_driver。它规定了tty驱动程序和高层之间的编程接口:

          struct  tty_driver [

              int  magic;

              /*  .  .  .  */

              int  major;

              int  minor_start;

              /*  .  .  .  */

              int  (*open)(struct  tty_struct  *tty,  struct  file  *flip);

              void  (*close)(struct  tty_struct  *tty,  struct  file  *flip);

              int  (*write)(struct  tty_struct  *tty,  const  unsigned  char  *buf,  int  count);

              void  (*ut_char)(struct  tty_struct  *tty, unsigned  char  ch);

              /*  .  .  .  .  */


     2、tty驱动程序需要两个步骤才能向内核注册自身:

       1)、通过调用tty_register_driver(struct  tty_driver  *tty_d)向tty核心注册自身。

       2)、调用下面的代码注册它支持的每个单独的tty:

           tty_register_device(struct  tty_driver  *tty_d,  unsigned  device_index,  struct  device  *device);



    五、线路规程

     1、线路规程提供了一套灵活的机制,使得用户运行不同的应用程序时,能顾使用相同的串行驱动程序。底层和物理驱动和tty驱动负责从硬件上收发数据,而线路规程则负责处

           理这些数据,并在内核空间和用户kona进之间传递数据。


     2、串行子程序支持17种标准的线路规程。线路规程也实现通过串行传输协议实现的网络接口。


     3、设备实例:触摸控制器

       1)、打开与关闭

          I、为了创建线路规程,需要定义struct  tty_idisc,并向内核注册指定的入口函数集。

          II、代码清单6-2:线路规程操作。

          III、代码清单6-3:打开线路规程。

       2)、读数据过程

          I、对于中断驱动的设备,读取数据的过程通常由一前一后两个线程组成

               由于发起读数据请求而从用户空间发起的底层线程。

               由中断处理程序(接受来自设备的数据)唤醒的底层线程。

          II、线路规程读数据过程(P137  图6-5)。

          III、代码清单6-4:n_touch_receive_buf()方法。

          IV、代码清单6-5:唤醒读线程和中止串行驱动程序。

       3)、写数据过程

       4)、I/O控制

          I、当应用程序打开串行设备时,它通常可向其发出一下三类ioctl。

               串行设备驱动程序支持的命令。

               tty驱动程序支持的命令。

               关联的线程规程支持的命令。

       5)、其他操作

       6)、修改线程规程。

          I、打码清单6-6:从用户空间修改线程规程。


    展开全文
  • LINUX下串行设备驱动程序

    千次阅读 2013-08-01 17:34:39
    在嵌入式或PC我们经常会遇到串行子系统,常遇到的场合如下: 通过RS-232串行链路运行终端会话;通过拔号、小区蜂窝或软件调制解调器连接到因特网;和触摸控制器、智能卡、蓝牙芯片或红外dongle等...1.LINUX下串行设备

        在嵌入式或PC我们经常会遇到串行子系统,常遇到的场合如下:

    • 通过RS-232串行链路运行终端会话;
    • 通过拔号、小区蜂窝或软件调制解调器连接到因特网;
    • 和触摸控制器、智能卡、蓝牙芯片或红外dongle等使用串行传输方式的设备接口;
    • 使用USB-串口端口的转换器模拟串行端口;
    • 通过RS-485通信,RS-485在RS-232的基础上支持多个节点,传输距离更远,抗噪性能更强.

    1.LINUX下串行设备驱动程序的层次架构:

        如图:

           

        因此,以下而上可以分为:硬件->UART底层驱动->TTY驱动->线路规程->用户空间

        关于其中串行子系统与内核源文件的映射关系如下图:

       

        [注:]LINUX下串行设备驱动和用户之间的交互并不像单纯的字符驱动程序那样将接口直接暴露给内核的系统调用.UART驱动程序是提供服务给另TTY层,再由TTY层传递给线路规程层,由线路规程层和用户空间的I/O调用关联.


    2.由上述可知,LINUX下串行口设备驱动主要包括三层:UART驱动层、TTY驱动层和线路规程三个层次组成.

        2-1.UART驱动层:

            UART驱动程序围绕3个关键的数据结构展开.这3个数据结构都定义于include/linux/serial_core.h

            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;
    
    	/*
    	 * 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设备赋值并进行一系列针对具体的UART的个性赋值.如S3C2440:

    static struct uart_driver s3c24xx_uart_drv = {
    	.owner		= THIS_MODULE,
    	.dev_name	= "s3c2410_serial",
    	.nr		= CONFIG_SERIAL_SAMSUNG_UARTS,
    	.cons		= S3C24XX_SERIAL_CONSOLE,
    	.driver_name	= S3C24XX_SERIAL_NAME,
    	.major		= S3C24XX_SERIAL_MAJOR,
    	.minor		= S3C24XX_SERIAL_MINOR,
    };

        2).struct uart_port.UART驱动程序拥有的每个端口都存在uart_port结构这样的一个实例.主要记录一些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);
    	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 */
    
    	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		unused[2];
    	void			*private_data;		/* generic platform data pointer */
    };
    
        和struct uart_driver一样,会被具体的uart port口重载.如S3C2440:

    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
    };
    

        3).struct uart_ops.这个结构是每个UART驱动程序必须支持的物理硬件上可完成的操作的入口函数的超集.如完成具体平台(如S3C2440)的寄存器配置、读写等动作.

    struct uart_port;
    struct serial_struct;
    struct device;
    
    /*
     * This structure describes all the operations that can be
     * done on the physical hardware.
     */
    struct uart_ops {
    	unsigned int	(*tx_empty)(struct uart_port *);
    	void		(*set_mctrl)(struct uart_port *, unsigned int mctrl);
    	unsigned int	(*get_mctrl)(struct uart_port *);
    	void		(*stop_tx)(struct uart_port *);
    	void		(*start_tx)(struct uart_port *);
    	void		(*send_xchar)(struct uart_port *, char ch);
    	void		(*stop_rx)(struct uart_port *);
    	void		(*enable_ms)(struct uart_port *);
    	void		(*break_ctl)(struct uart_port *, int ctl);
    	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 *);
    	void		(*pm)(struct uart_port *, unsigned int state,
    			      unsigned int oldstate);
    	int		(*set_wake)(struct uart_port *, unsigned int state);
    
    	/*
    	 * 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 *);
    	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
    	void	(*poll_put_char)(struct uart_port *, unsigned char);
    	int		(*poll_get_char)(struct uart_port *);
    #endif
    };
    
        此结构体与具体的硬件平台有关.如S3C2440:

    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,
    };
    

        UART驱动程序为了将自身和内核联系起来,必须完成两个重要的步骤:

        1).通过uart_register_driver(struct uart_driver)函数向串行核心注册;

        2).调用uart_add_one_port(struct uart_driver *,struct uart_port *)支持其支持的每个端口.


        小结:

            这些结构体数据及操作是LINUX平台UART驱动程序中最基本的共同点.


        实例:RS-485

            RS-232的传输距离只有数米,在嵌入式领域为了更长的传输距离,常常会采用RS-485,RS-485是半双工的UART操作.RS-485的常用电路图如下:

        因此,从FIFO发送数据至电缆之前,UART设备驱动程序需要禁用接收器,激活发送器.这个动作只需要设备相应的GPIO即可.因此,在串行层中恰当的时机激活/禁用RS-485的接收器/发送器.如果太早禁用了发送器,它有可能没有足够的时间清空发送FIFO中最后的几字节数据,这将导致数据丢失;太晚禁用发送器,就会阻止此段时间的数据接收,这将导致接收数据丢失.


        2-2.TTY驱动程序

            TTY驱动层有三个结构非常重要.

            1).定义于include/linux/tty.h中的struct tty_struct.此结构包含了打开的tty相关的所有状态信息.如下:

    /*
     * Where all of the state associated with a tty is kept while the tty
     * is open.  Since the termios state should be kept even if the tty
     * has been closed --- for things like the baud rate, etc --- it is
     * not stored here, but rather a pointer to the real state is stored
     * here.  Possible the winsize structure should have the same
     * treatment, but (1) the default 80x24 is usually right and (2) it's
     * most often used by a windowing system, which will set the correct
     * size each time the window is created or resized anyway.
     * 						- TYT, 9/14/92
     */
    
    struct tty_operations;
    
    struct tty_struct {
    	int	magic;
    	struct kref kref;
    	struct tty_driver *driver;
    	const struct tty_operations *ops;
    	int index;
    
    	/* Protects ldisc changes: Lock tty not pty */
    	struct mutex ldisc_mutex;
    	struct tty_ldisc *ldisc;
    
    	struct mutex termios_mutex;
    	spinlock_t ctrl_lock;
    	/* Termios values are protected by the termios mutex */
    	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;		/* termios mutex */
    	unsigned char stopped:1, hw_stopped:1, flow_stopped:1, packet:1;
    	unsigned char low_latency:1, warned:1;
    	unsigned char ctrl_status;	/* ctrl_lock */
    	unsigned int receive_room;	/* Bytes free for queue */
    
    	struct tty_struct *link;
    	struct fasync_struct *fasync;
    	struct tty_bufhead buf;		/* Locked internally */
    	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
    
    	/*
    	 * The following is data for the N_TTY line discipline.  For
    	 * historical reasons, this is included in the tty structure.
    	 * Mostly locked by the BKL.
    	 */
    	unsigned int column;
    	unsigned char lnext:1, erasing:1, raw:1, real_raw:1, icanon:1;
    	unsigned char closing:1;
    	unsigned char echo_overrun:1;
    	unsigned short minimum_to_wake;
    	unsigned long overrun_time;
    	int num_overrun;
    	unsigned long process_char_map[256/(8*sizeof(unsigned long))];
    	char *read_buf;
    	int read_head;
    	int read_tail;
    	int read_cnt;
    	unsigned long read_flags[N_TTY_BUF_SIZE/(8*sizeof(unsigned long))];
    	unsigned char *echo_buf;
    	unsigned int echo_pos;
    	unsigned int echo_cnt;
    	int canon_data;
    	unsigned long canon_head;
    	unsigned int canon_column;
    	struct mutex atomic_read_lock;
    	struct mutex atomic_write_lock;
    	struct mutex output_lock;
    	struct mutex echo_lock;
    	unsigned char *write_buf;
    	int write_cnt;
    	spinlock_t read_lock;
    	/* If the tty has a pending do_SAK, queue it here - akpm */
    	struct work_struct SAK_work;
    	struct tty_port *port;
    };
    

        2).tty_struct中内嵌的struct tty_flip_buffer或flip缓冲区.这是数据收集和处理机制的中枢.底层品行驱动程序将flip缓冲区的一半用于数据收集,线路规程则使用另一半来进行数据处理.在高版本的内核对上述的flip缓冲区机制作了取代.由缓冲区头部(tty_bufhead)和缓冲区链表(tty_buffer)组成.见上述的结构体成员:
    struct tty_bufhead buf;
        展开后如下:

    struct tty_bufhead {
    	struct delayed_work work;
    	spinlock_t lock;
    	struct tty_buffer *head;	/* Queue head */
    	struct tty_buffer *tail;	/* Active buffer */
    	struct tty_buffer *free;	/* Free queue head */
    	int memory_used;		/* Buffer space used excluding
    								free queue */
    };
    

    struct tty_buffer {
    	struct tty_buffer *next;
    	char *char_buf_ptr;
    	unsigned char *flag_buf_ptr;
    	int used;
    	int size;
    	int commit;
    	int read;
    	/* Data points here */
    	unsigned long data[0];
    };
    

        3).定义于include/linux/tty_driver.h文件中的struct tty_driver.它规定了tty驱动程序和高层之间的编程接口:

    struct tty_driver {
    	int	magic;		/* magic number for this structure */
    	struct kref kref;	/* Reference management */
    	struct cdev cdev;
    	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 */
    	int	minor_num;	/* number of *possible* devices */
    	int	num;		/* number of devices allocated */
    	short	type;		/* type of tty driver */
    	short	subtype;	/* subtype of tty driver */
    	struct ktermios init_termios; /* Initial termios */
    	int	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 ktermios **termios;
    	struct ktermios **termios_locked;
    	void *driver_state;
    
    	/*
    	 * Driver methods
    	 */
    
    	const struct tty_operations *ops;
    	struct list_head tty_drivers;
    };
    

        tty驱动程序需要完成两个步骤向内核注册自身:

        1).调用tty_register_driver(struct tty_driver *tty_d)向tty核心注册自身;

        2).调用以下代码注册它支持的每个单独的tty:

    struct device *tty_register_device(struct tty_driver *driver, unsigned index,struct device *device)


        2-3.线路规程

        线路规程的意义:

            线路规程是一种策略:使得用户运行不同的应用程序时,可以使用相同的底层串行驱动程序.

        线路规程的具体工作内容:

            负责处理这些数据,其工作 内容就是以特殊的方式格式化从一个用户或者硬件收到的数据,这种格式化往往采用一个协议转换的形式,如PPP和Bluetooth.

        线路规程在LINUX UART子系统中的地位:

            tty核心从一个用户获取将要发送给一个tty设备的数据,tty核心将数据传递给tty线路规程驱动,接着将数据传递到tty驱动,tty驱动将数据转换为可以发送给硬件的格式.(下面将以三星S3C2440 UART为实例分析此过程).

        目前串行子系统支持17种标准的线路规程.常用的线路规程如下:

        N_TTY:默认的线路规程,主要实现字符文本的处理,如cat 、echo 字符;

        N_PPP:点对点协议;

        N_SLIP:串行线路网际协议;

        N_IRDA:红外数据;

        N_HCI:蓝牙主机控制接口.


    3.UART驱动层、TTY驱动层和线路规程三个层次的关系:

        UART驱动层、TTY驱动层和线路规程组成了LINUX下的串行设备驱动程序.整个框架如下:


    先从宏观的结构体看这三个层次的关系.

    UART驱动层 -> TTY层:

    include/linux/serial_core.h

    struct uart_driver {
            ... ...;
    	struct tty_driver	*tty_driver;
    };
    struct uart_driver是一个具体特定的UART设备.里面封装了struct tty_driver,tty_driver是一个直接面向用户空间的字符设备.如下:

    struct tty_driver {
            ... ...;
    	struct cdev cdev;
            ... ...;
    };
    
    struct tty_struct表征一个终端设备.它和线路规程密切关联:

    struct tty_struct {
            ... ...;
    	struct tty_ldisc *ldisc;
            ... ...;
    };
    struct tty_ldisc最主要的是包含了对线路规程的设置操作集:

    struct tty_ldisc {
    	struct tty_ldisc_ops *ops;
    	atomic_t users;
    };
    线路规程的操作集都是面向TTY设备终端的:

    struct tty_ldisc_ops {
    	int	magic;
    	char	*name;
    	int	num;
    	int	flags;
    	
    	/*
    	 * The following routines are called from above.
    	 */
    	int	(*open)(struct tty_struct *);
    	void	(*close)(struct tty_struct *);
    	void	(*flush_buffer)(struct tty_struct *tty);
    	ssize_t	(*chars_in_buffer)(struct tty_struct *tty);
    	ssize_t	(*read)(struct tty_struct * tty, struct file * file,
    			unsigned char __user * buf, size_t nr);
    	ssize_t	(*write)(struct tty_struct * tty, struct file * file,
    			 const unsigned char * buf, size_t nr);	
    	int	(*ioctl)(struct tty_struct * tty, struct file * file,
    			 unsigned int cmd, unsigned long arg);
    	long	(*compat_ioctl)(struct tty_struct * tty, struct file * file,
    				unsigned int cmd, unsigned long arg);
    	void	(*set_termios)(struct tty_struct *tty, struct ktermios * old);
    	unsigned int (*poll)(struct tty_struct *, struct file *,
    			     struct poll_table_struct *);
    	int	(*hangup)(struct tty_struct *tty);
    	
    	/*
    	 * The following routines are called from below.
    	 */
    	void	(*receive_buf)(struct tty_struct *, const unsigned char *cp,
    			       char *fp, int count);
    	void	(*write_wakeup)(struct tty_struct *);
    
    	struct  module *owner;
    	
    	int refcount;
    };
    
    可见,其所有操作函数都以struct tty_struct作为参数.


    4.实例分析:

        LINUX UART驱动子系统和大多数的LINUX子系统一样,比如说IIC、SPI、USB、FBI等.均采用了分层的架构思想.每一层都有其操作集.但是无论如何分层(各种子系统),暴露给用户空间的分类只有三种:字符设备、块设备、网络设备.LINUX内核之所以采用分层思想,只要是为了代码的重用性、可移植性.尽可能地把和具体平台相关的代码达到最大程序的隔离.但是这导致了初学者对内核阅读的难度.


    ======================================================== UART 驱 动 层 ================================================================

        下面以S3C2440的UART驱动对内核这种子系统分层系统进行宏观认识.从用户空间到最底层的寄存器设置进行代码跟踪分析.

        S3C2440 UART的入口:drivers/serial/samsung.c

    module_init(s3c24xx_serial_modinit);
    -->

    static int __init s3c24xx_serial_modinit(void)
    {
            ... ...;
    	ret = uart_register_driver(&s3c24xx_uart_drv);
            ... ...;
    }
    -->

    static struct uart_driver s3c24xx_uart_drv = {
    	.owner		= THIS_MODULE,
    	.dev_name	= "s3c2410_serial",
    	.nr		= CONFIG_SERIAL_SAMSUNG_UARTS,
    	.cons		= S3C24XX_SERIAL_CONSOLE,
    	.driver_name	= S3C24XX_SERIAL_NAME,
    	.major		= S3C24XX_SERIAL_MAJOR,
    	.minor		= S3C24XX_SERIAL_MINOR,
    };
    -->    

        drivers/serial/serial_core.c

        上述分析了,struct uart_driver是表征一个具体的UART设备.因此,三星的S3C2440的UART是一个具体的UART设备.由于直接面向用户空间的字符设备是tty.因此,这个tty也必须是与S3C2440这个平台相关的.而在UART层与用户空间没有直接的关系.下面分析函数uart_register_driver()函数:

    int uart_register_driver(struct uart_driver *drv)
    {
    	struct tty_driver *normal = NULL;
    	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.
    	 */
    	drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
    	retval = -ENOMEM;
    	if (!drv->state)
    		goto out;
    
    	normal  = alloc_tty_driver(drv->nr);
    	if (!normal)
    		goto out;
    
    	drv->tty_driver = normal;
    
    	normal->owner		= drv->owner;
    	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->close_delay     = 500;	/* .5 seconds */
    		port->closing_wait    = 30000;	/* 30 seconds */
    		tasklet_init(&state->tlet, uart_tasklet_action,
    			     (unsigned long)state);
    	}
    
    	retval = tty_register_driver(normal);
     out:
    	if (retval < 0) {
    		put_tty_driver(normal);
    		kfree(drv->state);
    	}
    	return retval;
    }
        由此可见,根据S3C2440这个平台相关的UART信息初始化一个与S3C2440这个平台相关的TTY.比如,主次设备号、驱动名等.


    ===================================================== TTY 层 驱 动 =====================================================================

    tty_set_operations(normal, &uart_ops);
       实现了对这个tty的操作集初始化.其中uar_ops是一个全局变量.如下:

    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,
    #ifdef CONFIG_CONSOLE_POLL
    	.poll_init	= uart_poll_init,
    	.poll_get_char	= uart_poll_get_char,
    	.poll_put_char	= uart_poll_put_char,
    #endif
    };
    
        既然uart_ops是一个全局变量,而且位置serail core.说明此变量是与平台无关的.这里的函数只是一种"形式".并不具备实际的硬件操作能力.只是为了软件上的统一组织.后面的分析将看到这一点.

    -->

        drivers/char/tty_io.c

        用这个特定的UART信息初始完成的tty.便要注册到tty驱动层.下面分析函数tty_register_driver().

    /*
     * Called by a tty driver to register itself.
     */
    int tty_register_driver(struct tty_driver *driver)
    {
    	int error;
    	int i;
    	dev_t dev;
    	void **p = NULL;
    
    	if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM) && driver->num) {
    		p = kzalloc(driver->num * 2 * sizeof(void *), GFP_KERNEL);
    		if (!p)
    			return -ENOMEM;
    	}
    
    	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) {
    		kfree(p);
    		return error;
    	}
    
    	if (p) {
    		driver->ttys = (struct tty_struct **)p;
    		driver->termios = (struct ktermios **)(p + driver->num);
    	} else {
    		driver->ttys = NULL;
    		driver->termios = NULL;
    	}
    
    	cdev_init(&driver->cdev, &tty_fops);
    	driver->cdev.owner = driver->owner;
    	error = cdev_add(&driver->cdev, dev, driver->num);
    	if (error) {
    		unregister_chrdev_region(dev, driver->num);
    		driver->ttys = NULL;
    		driver->termios = NULL;
    		kfree(p);
    		return error;
    	}
    
    	mutex_lock(&tty_mutex);
    	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++)
    		    tty_register_device(driver, i, NULL);
    	}
    	proc_tty_register_driver(driver);
    	driver->flags |= TTY_DRIVER_INSTALLED;
    	return 0;
    }
    
        注意到上面代码中下面语句:

    cdev_init(&driver->cdev, &tty_fops);
        这条语句的执行,tty正式以字符设备的形式暴露给用户空间.其中,tty_fops是一个全局变量,因此,它也是与具体平台无关的.因为此部分是tty驱动层的公共函数.如下:

    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_register_device()完成.展开如下:

    struct device *tty_register_device(struct tty_driver *driver, unsigned index,
    				   struct device *device)
    {
    	char name[64];
    	dev_t dev = MKDEV(driver->major, driver->minor_start) + index;
    
    	if (index >= driver->num) {
    		printk(KERN_ERR "Attempt to register invalid tty line number "
    		       " (%d).\n", index);
    		return ERR_PTR(-EINVAL);
    	}
    
    	if (driver->type == TTY_DRIVER_TYPE_PTY)
    		pty_line_name(driver, index, name);
    	else
    		tty_line_name(driver, index, name);
    
    	return device_create(tty_class, device, dev, NULL, name);
    }
        此函数在此在一个for循环里面调用,里面封装了device_create()函数,在udev支持下动态生成设备节点如下:
    /dev/ttySAC0 c 204 64
    /dev/ttySAC1 c 204 65
    /dev/ttySAC2 c 204 66

        但是真正生成字符设备节点的并不是在这里,因为上述的driver->flag标志了TTY_DRIVER_DYNAMIC_DEV.因此,这将在后续注册uart port的时候再动态生成,也就是说,这里的if条件是不成立的.

        那么,在什么时机生成特定的UART设备字符设备节点呢?下面跟踪此源码部分.此下又回到了UART驱动层.见源码位置:driver/serial/s3c2440.c

    ========================================================UART驱动层==================================================================

        在分析源码之前,有个结构体是不是不认识一下的,因为它关联了struct uart_driver、struct uart_port、struct tty_struct三者的关系.其中struct uart_driver是我们平台相关的UART驱动;struct uart_port是这个平台相关的UART驱动管辖的port口,这涉及到具体平台的硬件寄存器的配置;struct tty_struct是用户空间直接面向的tty设备.这个结构体就是struct uart_state.这个结构体的生成在uart_register_driver()函数.分配并赋予uart_driver的state域.

        下面分析struct uart_state的重大意义,源码入口位置为drivers/serial/s3c2440.c:

    module_init(s3c2440_serial_init);
    -->

    static int __init s3c2440_serial_init(void)
    {
    	return s3c24xx_serial_init(&s3c2440_serial_driver, &s3c2440_uart_inf);
    }
    -->

    int s3c24xx_serial_init(struct platform_driver *drv,
    			struct s3c24xx_uart_info *info)
    {
    	dbg("s3c24xx_serial_init(%p,%p)\n", drv, info);
    
    #ifdef CONFIG_PM
    	drv->suspend = s3c24xx_serial_suspend;
    	drv->resume = s3c24xx_serial_resume;
    #endif
    
    	return platform_driver_register(drv);
    }
        由于LINUX平台总线的规划,直接导致s3c2440_serial_driver的函数probe域的执行(过程的调用关系日后有空再作分析):

    static struct platform_driver s3c2440_serial_driver = {
    	.probe		= s3c2440_serial_probe,
    	.remove		= __devexit_p(s3c24xx_serial_remove),
    	.driver		= {
    		.name	= "s3c2440-uart",
    		.owner	= THIS_MODULE,
    	},
    };
    -->

    static int s3c2440_serial_probe(struct platform_device *dev)
    {
    	dbg("s3c2440_serial_probe: dev=%p\n", dev);
    	return s3c24xx_serial_probe(dev, &s3c2440_uart_inf);
    }
    -->

    int s3c24xx_serial_probe(struct platform_device *dev,
    			 struct s3c24xx_uart_info *info)
    {
    	struct s3c24xx_uart_port *ourport;
    	int ret;
    
    	dbg("s3c24xx_serial_probe(%p, %p) %d\n", dev, info, probe_index);
    
    	ourport = &s3c24xx_serial_ports[probe_index];
    	probe_index++;
    
    	dbg("%s: initialising port %p...\n", __func__, ourport);
    
    	ret = s3c24xx_serial_init_port(ourport, info, dev);
    	if (ret < 0)
    		goto probe_err;
    
    	dbg("%s: adding port\n", __func__);
    	uart_add_one_port(&s3c24xx_uart_drv, &ourport->port);
    	platform_set_drvdata(dev, &ourport->port);
    
    	ret = device_create_file(&dev->dev, &dev_attr_clock_source);
    	if (ret < 0)
    		printk(KERN_ERR "%s: failed to add clksrc attr.\n", __func__);
    
    	ret = s3c24xx_serial_cpufreq_register(ourport);
    	if (ret < 0)
    		dev_err(&dev->dev, "failed to add cpufreq notifier\n");
    
    	return 0;
    
     probe_err:
    	return ret;
    }
    
        在这里出现一个极其重要的全局数组s3c24xx_serial_ports和一个极其重要的函数uart_add_one_port().把s3c2440_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.展开如下:

    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,
    };
    
        至此,我们可以猜测,用户空间的系统调用(比如设置波特率)必定调用到此才算完成底层硬件的通讯协议的配置.那么,用户空间的系统调用如何路由到这里呢?通过struct uart_state来桥接.这种桥接关系在函数uart_add_one_port()里面实现.展开uart_add_one_port()函数:

    int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
    {
    	struct uart_state *state;
    	struct tty_port *port;
    	int ret = 0;
    	struct device *tty_dev;
    
    	BUG_ON(in_interrupt());
    
    	if (uport->line >= drv->nr)
    		return -EINVAL;
    
    	state = drv->state + uport->line;
    	port = &state->port;
    
    	mutex_lock(&port_mutex);
    	mutex_lock(&port->mutex);
    	if (state->uart_port) {
    		ret = -EINVAL;
    		goto out;
    	}
    
    	state->uart_port = uport;
    	state->pm_state = -1;
    
    	uport->cons = drv->cons;
    	uport->state = state;
    
    	/*
    	 * If this port is a console, then the spinlock is already
    	 * initialised.
    	 */
    	if (!(uart_console(uport) && (uport->cons->flags & CON_ENABLED))) {
    		spin_lock_init(&uport->lock);
    		lockdep_set_class(&uport->lock, &port_lock_key);
    	}
    
    	uart_configure_port(drv, state, uport);
    
    	/*
    	 * Register the port whether it's detected or not.  This allows
    	 * setserial to be used to alter this ports parameters.
    	 */
    	tty_dev = tty_register_device(drv->tty_driver, uport->line, uport->dev);
    	if (likely(!IS_ERR(tty_dev))) {
    		device_init_wakeup(tty_dev, 1);
    		device_set_wakeup_enable(tty_dev, 0);
    	} else
    		printk(KERN_ERR "Cannot register tty device on line %d\n",
    		       uport->line);
    
    	/*
    	 * Ensure UPF_DEAD is not set.
    	 */
    	uport->flags &= ~UPF_DEAD;
    
     out:
    	mutex_unlock(&port->mutex);
    	mutex_unlock(&port_mutex);
    
    	return ret;
    }
    
        下面大致针对struct uart_state这个结构体的桥接的意义分析一下相关代码:

    struct uart_state *state;
    ... ...;
    state = drv->state + uport->line;
        临时指针state关联了uart_driver的state域.

    state->uart_port = uport;
        state和具体的uart port口关联起来了.实际上,是把uart_driver和其uart port口关联了.

    此函数里面还有一个很重要的函数:tty_register_device().展开如下:

    
    struct device *tty_register_device(struct tty_driver *driver, unsigned index,
    				   struct device *device)
    {
    	char name[64];
    	dev_t dev = MKDEV(driver->major, driver->minor_start) + index;
    
    	if (index >= driver->num) {
    		printk(KERN_ERR "Attempt to register invalid tty line number "
    		       " (%d).\n", index);
    		return ERR_PTR(-EINVAL);
    	}
    
    	if (driver->type == TTY_DRIVER_TYPE_PTY)
    		pty_line_name(driver, index, name);
    	else
    		tty_line_name(driver, index, name);
    
    	return device_create(tty_class, device, dev, NULL, name);
    }
    
        在此,正式动态生成了设备节点,比如/dev/ttySAC0 c 204 64

        [注:]这里只生成一个设备节点,但是S3C2440有三个串口,为何只生成一个呢?其实,每个串口都被注册为平台设备,也就是说probe函数会被执行三次.每次生成一个设备节点,因此,一共会存在三个设备节点.


        至此,每一次probe,完成了一个字符设备的注册了.这三个个字符设备的设备节点为:

    /dev/ttySAC0 c 204 64
    /dev/ttySAC1 c 204 65
    /dev/ttySAC2 c 204 66
        这三个字符设备关联的操作集是uart_ops(见上述函数uart_register_driver分析)而不是与具体uart port的操作集s3c24xx_serial_ops.因为分层结构导致了用户空间没办法直接面向底层硬件的操作.用户空间直接调用的是tty_fops,tty_fops会被uart_ops重载,而这过程中相应的s3c24xx_serial_ops会重载uart_ops相应的调用.实现用户到具体底层的间接调用.


    ====================================================== 从用户空间到底层===============================================================

        当用户空间对这个tty字符设备进行open()、read()等系统调用的时候.会导致tty_fops这些函数的直接运行.下面先以tty_read()函数分析:

    static ssize_t tty_read(struct file *file, char __user *buf, size_t count,
    			loff_t *ppos)
    {
    	int i;
    	struct tty_struct *tty;
    	struct inode *inode;
    	struct tty_ldisc *ld;
    
    	tty = (struct tty_struct *)file->private_data;
    	inode = file->f_path.dentry->d_inode;
    	if (tty_paranoia_check(tty, inode, "tty_read"))
    		return -EIO;
    	if (!tty || (test_bit(TTY_IO_ERROR, &tty->flags)))
    		return -EIO;
    
    	/* We want to wait for the line discipline to sort out in this
    	   situation */
    	ld = tty_ldisc_ref_wait(tty);
    	if (ld->ops->read)
    		i = (ld->ops->read)(tty, file, buf, count);
    	else
    		i = -EIO;
    	tty_ldisc_deref(ld);
    	if (i > 0)
    		inode->i_atime = current_fs_time(inode->i_sb);
    	return i;
    }
        注意有上述有行代码:

    	tty = (struct tty_struct *)file->private_data;
    	ld = tty_ldisc_ref_wait(tty);
    	if (ld->ops->read)
    		i = (ld->ops->read)(tty, file, buf, count);

        我们知道,inode是物理文件,存在唯一性;file是open时候动态创建的.大概猜测:open的时候把open的特定的tty设备保存在file->private_data.这个域是保存的是文件的私有信息.然后通过函数tty_ldisc_ref_wait()找到此tty的路线规程.然后再调用被重载的线路规程的read函数:ld->ops->read.因此,tty_open()函数必大有乾坤.下面分析tty_open()函数:

    static int tty_open(struct inode *inode, struct file *filp)
    {
    	int ret;
    
    	lock_kernel();
    	ret = __tty_open(inode, filp);
    	unlock_kernel();
    	return ret;
    }
    -->

    static int __tty_open(struct inode *inode, struct file *filp)
    {
    	struct tty_struct *tty = NULL;
    	int noctty, retval;
    	struct tty_driver *driver;
    	int index;
    	dev_t device = inode->i_rdev;
    	unsigned saved_flags = filp->f_flags;
    
    	nonseekable_open(inode, filp);
    
    retry_open:
    	noctty = filp->f_flags & O_NOCTTY;
    	index  = -1;
    	retval = 0;
    
    	mutex_lock(&tty_mutex);
    
    	if (device == MKDEV(TTYAUX_MAJOR, 0)) {
    		tty = get_current_tty();
    		if (!tty) {
    			mutex_unlock(&tty_mutex);
    			return -ENXIO;
    		}
    		driver = tty_driver_kref_get(tty->driver);
    		index = tty->index;
    		filp->f_flags |= O_NONBLOCK; /* Don't let /dev/tty block */
    		/* noctty = 1; */
    		/* FIXME: Should we take a driver reference ? */
    		tty_kref_put(tty);
    		goto got_driver;
    	}
    #ifdef CONFIG_VT
    	if (device == MKDEV(TTY_MAJOR, 0)) {
    		extern struct tty_driver *console_driver;
    		driver = tty_driver_kref_get(console_driver);
    		index = fg_console;
    		noctty = 1;
    		goto got_driver;
    	}
    #endif
    	if (device == MKDEV(TTYAUX_MAJOR, 1)) {
    		struct tty_driver *console_driver = console_device(&index);
    		if (console_driver) {
    			driver = tty_driver_kref_get(console_driver);
    			if (driver) {
    				/* Don't let /dev/console block */
    				filp->f_flags |= O_NONBLOCK;
    				noctty = 1;
    				goto got_driver;
    			}
    		}
    		mutex_unlock(&tty_mutex);
    		return -ENODEV;
    	}
    
    	driver = get_tty_driver(device, &index);
    	if (!driver) {
    		mutex_unlock(&tty_mutex);
    		return -ENODEV;
    	}
    got_driver:
    	if (!tty) {
    		/* check whether we're reopening an existing tty */
    		tty = tty_driver_lookup_tty(driver, inode, index);
    
    		if (IS_ERR(tty)) {
    			mutex_unlock(&tty_mutex);
    			return PTR_ERR(tty);
    		}
    	}
    
    	if (tty) {
    		retval = tty_reopen(tty);
    		if (retval)
    			tty = ERR_PTR(retval);
    	} else
    		tty = tty_init_dev(driver, index, 0);
    
    	mutex_unlock(&tty_mutex);
    	tty_driver_kref_put(driver);
    	if (IS_ERR(tty))
    		return PTR_ERR(tty);
    
    	filp->private_data = tty;
    	file_move(filp, &tty->tty_files);
    	check_tty_count(tty, "tty_open");
    	if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
    	    tty->driver->subtype == PTY_TYPE_MASTER)
    		noctty = 1;
    #ifdef TTY_DEBUG_HANGUP
    	printk(KERN_DEBUG "opening %s...", tty->name);
    #endif
    	if (!retval) {
    		if (tty->ops->open)
    			retval = tty->ops->open(tty, filp);
    		else
    			retval = -ENODEV;
    	}
    	filp->f_flags = saved_flags;
    
    	if (!retval && test_bit(TTY_EXCLUSIVE, &tty->flags) &&
    						!capable(CAP_SYS_ADMIN))
    		retval = -EBUSY;
    
    	if (retval) {
    #ifdef TTY_DEBUG_HANGUP
    		printk(KERN_DEBUG "error %d in opening %s...", retval,
    		       tty->name);
    #endif
    		tty_release_dev(filp);
    		if (retval != -ERESTARTSYS)
    			return retval;
    		if (signal_pending(current))
    			return retval;
    		schedule();
    		/*
    		 * Need to reset f_op in case a hangup happened.
    		 */
    		if (filp->f_op == &hung_up_tty_fops)
    			filp->f_op = &tty_fops;
    		goto retry_open;
    	}
    
    	mutex_lock(&tty_mutex);
    	spin_lock_irq(¤t->sighand->siglock);
    	if (!noctty &&
    	    current->signal->leader &&
    	    !current->signal->tty &&
    	    tty->session == NULL)
    		__proc_set_tty(current, tty);
    	spin_unlock_irq(¤t->sighand->siglock);
    	mutex_unlock(&tty_mutex);
    	return 0;
    }
    
       

        注意到上述中下面代码:

    tty = tty_init_dev(driver, index, 0);
    
    	filp->private_data = tty;
        可见,open的时候,把这个tty保存在filp这个文件结构体中的私有域filp->private_data中.tty出生于函数tty_init_dev()函数里面:包括这个tty从无到有的内存分配、初始化、关联相应的线路规程等动作均在此函数完成.因此,此函数不得不窥视一下:

    struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx,int first_ok)
    {
    	struct tty_struct *tty;
    	int retval;
    
    	/* Check if pty master is being opened multiple times */
    	if (driver->subtype == PTY_TYPE_MASTER &&
    		(driver->flags & TTY_DRIVER_DEVPTS_MEM) && !first_ok)
    		return ERR_PTR(-EIO);
    
    	/*
    	 * First time open is complex, especially for PTY devices.
    	 * This code guarantees that either everything succeeds and the
    	 * TTY is ready for operation, or else the table slots are vacated
    	 * and the allocated memory released.  (Except that the termios
    	 * and locked termios may be retained.)
    	 */
    
    	if (!try_module_get(driver->owner))
    		return ERR_PTR(-ENODEV);
    
    	tty = alloc_tty_struct();
    	if (!tty)
    		goto fail_no_mem;
    	initialize_tty_struct(tty, driver, idx);
    
    	retval = tty_driver_install_tty(driver, tty);
    	if (retval < 0) {
    		free_tty_struct(tty);
    		module_put(driver->owner);
    		return ERR_PTR(retval);
    	}
    
    	/*
    	 * Structures all installed ... call the ldisc open routines.
    	 * If we fail here just call release_tty to clean up.  No need
    	 * to decrement the use counts, as release_tty doesn't care.
    	 */
    
    	retval = tty_ldisc_setup(tty, tty->link);
    	if (retval)
    		goto release_mem_out;
    	return tty;
    
    fail_no_mem:
    	module_put(driver->owner);
    	return ERR_PTR(-ENOMEM);
    
    	/* call the tty release_tty routine to clean out this slot */
    release_mem_out:
    	if (printk_ratelimit())
    		printk(KERN_INFO "tty_init_dev: ldisc open failed, "
    				 "clearing slot %d\n", idx);
    	release_tty(tty, idx);
    	return ERR_PTR(retval);
    }
    
        上面代码中,重点关注三个函数:

    tty = alloc_tty_struct();
        动态分配了一个tty设备结构体,这里tty结构体就表征一个设备.接下来会必将会对此tty设备进行初始化,最重要的是初始化这个设备的操作集(一个设备不提供操作方法此设备又有何存在意义?).

    initialize_tty_struct(tty, driver, idx);
        初始化tty设备,主要用driver(这里是tty_driver,是tty层的驱动.见更上面的uart_register_driver()函数分析)来初始化此设备:

    void initialize_tty_struct(struct tty_struct *tty,
    		struct tty_driver *driver, int idx)
    {
    	memset(tty, 0, sizeof(struct tty_struct));
    	kref_init(&tty->kref);
    	tty->magic = TTY_MAGIC;
    	tty_ldisc_init(tty);
    	tty->session = NULL;
    	tty->pgrp = NULL;
    	tty->overrun_time = jiffies;
    	tty->buf.head = tty->buf.tail = NULL;
    	tty_buffer_init(tty);
    	mutex_init(&tty->termios_mutex);
    	mutex_init(&tty->ldisc_mutex);
    	init_waitqueue_head(&tty->write_wait);
    	init_waitqueue_head(&tty->read_wait);
    	INIT_WORK(&tty->hangup_work, do_tty_hangup);
    	mutex_init(&tty->atomic_read_lock);
    	mutex_init(&tty->atomic_write_lock);
    	mutex_init(&tty->output_lock);
    	mutex_init(&tty->echo_lock);
    	spin_lock_init(&tty->read_lock);
    	spin_lock_init(&tty->ctrl_lock);
    	INIT_LIST_HEAD(&tty->tty_files);
    	INIT_WORK(&tty->SAK_work, do_SAK_work);
    
    	tty->driver = driver;
    	tty->ops = driver->ops;
    	tty->index = idx;
    	tty_line_name(driver, idx, tty->name);
    }
    
        上述代码中需要着重分析的代码有两行:

    tty->ops = driver ->ops;
        用tty_driver->ops来初始化设备tty->ops.由前面分析,tty_driver->ops是一个平台无关的"虚操作集"--即其并没有具备实际的硬件平台操作能力,这纯粹是为了软件的统一管理.

    那么,tty对应在驱动driver是从何处来呢?回到__tty_open(),见下面语句:

    	driver = get_tty_driver(device, &index);
    此函数展开如下:

    static struct tty_driver *get_tty_driver(dev_t device, int *index)
    {
    	struct tty_driver *p;
    
    	list_for_each_entry(p, &tty_drivers, tty_drivers) {
    		dev_t base = MKDEV(p->major, p->minor_start);
    		if (device < base || device >= base + p->num)
    			continue;
    		*index = device - base;
    		return tty_driver_kref_get(p);
    	}
    	return NULL;
    }
        这主要策略是通过设备文件的设备号来获取其相应的驱动,即在上述分析的函数uart_register_driver()中的全局变量的操作集uart_ops.因此,接下来的语句:

    if (tty->ops->open)
    			retval = tty->ops->open(tty, filp);
    实际调用的是uart_ops中的open函数.如下:

    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(!kernel_locked());
    	pr_debug("uart_open(%d) called\n", line);
    
    	/*
    	 * tty->driver->num won't change, so we won't fail here with
    	 * tty->driver_data set to something non-NULL (and therefore
    	 * we won't get caught by uart_close()).
    	 */
    	retval = -ENODEV;
    	if (line >= tty->driver->num)
    		goto fail;
    
    	/*
    	 * 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(state, 0);
    
    	/*
    	 * If we succeeded, wait until the port is ready.
    	 */
    	if (retval == 0)
    		retval = uart_block_til_ready(filp, state);
    	mutex_unlock(&port->mutex);
    
    	/*
    	 * If this is the first open to succeed, adjust things to suit.
    	 */
    	if (retval == 0 && !(port->flags & ASYNC_NORMAL_ACTIVE)) {
    		set_bit(ASYNCB_NORMAL_ACTIVE, &port->flags);
    
    		uart_update_termios(state);
    	}
    
    fail:
    	return retval;
    }
    

        此函数的深入将涉及到tty设备与具体的硬件串口、寄存器配置关联.后面分析.

    =========================================================线 路 规 程 =============================================================

        上述函数initialize_tty_struct()实现tty设备的初始化,其中就包括了线路规程的配置.

    drivers/char/tty_ldisc.c

    tty_ldisc_init(tty);
        对此tty设备的线路规程初始化:

    void tty_ldisc_init(struct tty_struct *tty)
    {
    	struct tty_ldisc *ld = tty_ldisc_get(N_TTY);
    	if (IS_ERR(ld))
    		panic("n_tty: init_tty");
    	tty_ldisc_assign(tty, ld);
    }
    
        因此,默认的线路规程是N_TTY.继承分析tty_ldisc_get()函数:

    static struct tty_ldisc *tty_ldisc_get(int disc)
    {
            ... ...;
    	ldops = get_ldops(disc);
            ... ...;
    }
    
        我们给函数get_ldops()函数传递进去的参数是disc = N_TTY.展开get_ldops()函数:

    static struct tty_ldisc_ops *get_ldops(int disc)
    {
    	unsigned long flags;
    	struct tty_ldisc_ops *ldops, *ret;
    
    	spin_lock_irqsave(&tty_ldisc_lock, flags);
    	ret = ERR_PTR(-EINVAL);
    	ldops = tty_ldiscs[disc];
    	if (ldops) {
    		ret = ERR_PTR(-EAGAIN);
    		if (try_module_get(ldops->owner)) {
    			ldops->refcount++;
    			ret = ldops;
    		}
    	}
    	spin_unlock_irqrestore(&tty_ldisc_lock, flags);
    	return ret;
    }
    
        上述函数中,最重要的语句为:

    ldops = tty_ldiscs[disc];
        其中tty_ldiscs是一个全局数组,此数组里面每个元素都是一种线路规程的操作集.LK2.6.32.2一共支持有20种线路规程.如下:

    static struct tty_ldisc_ops *tty_ldiscs[NR_LDISCS];
    
        我们传递进去的数组的索引值是N_TTY(实际是数值0).但是N_TTY对应的实际的操作值是什么?在哪里面对这N_TTY这种线路规程初始化呢?答案是在内核启动过程对控制台初始化的时候对tty_ldiscs[N_TTY]初始化了.如下:

        init/main.c

    void __init console_init(void)
    {
    	initcall_t *call;
    
    	/* Setup the default TTY line discipline. */
    	tty_ldisc_begin();
    
    	/*
    	 * set up the console device so that later boot sequences can
    	 * inform about problems etc..
    	 */
    	call = __con_initcall_start;
    	while (call < __con_initcall_end) {
    		(*call)();
    		call++;
    	}
    }
    展开函数tty_ldisc_begin():

    void tty_ldisc_begin(void)
    {
    	/* Setup the default TTY line discipline. */
    	(void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);
    }
    
        其中,tty_ldisc_N_TTY便是N_TTY对应的线路规程.如下:

    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
    };
    
        至此,把这个tty设备的线路规程配置为N_TTY.用户空间操作tty这个字符设备时,比如open,会被这个tty设备的线路规程操作集重载.上述已经分析了.见上面的tty_read()函数的简析.

        我们open一个tty终端设备之后往往需要对其进行一些波特率之类的设置,便是对线路规程的设置.对应上述的set_termios域.

    因此,到这里,我们应该明确.用户空间进行某tty设备这个字符设备进行系统调用的时候,首先这个tty设备相应的"虚操作集"(即tty_fops)会被调用,在这"虚操作集"中又被此tty设备的线路规程重载了,指定的线路规程相应的"操作集"被调用了.下面是示意代码:

    open("/dev/ttyS0") -->tty_open()-->n_tty_open()

    分别对应:user-space --> tty-core->tty-ldisc


    ======================================================== 具体平台操作===============================================================

        上面我们已经分析出TTY字符设备节点如何生成、用户空间的系统调用首先直接面向tty_fops的操作集、tty_fops又被线路规程的tty_ldisc_ops重载了.相当于用户空间间接调用了tty_ldisc_ops.但是在此我们并没有看到具体的uart port操作,因为如果至此为止的话,上面的所有功夫都没有意义,因为只有底层硬件协议生效(指配置寄存器)才可以驱动外设.下面从用户空间到最底层的寄存器操作流程进行分析.为了连贯性.下面的分析可能会和上述有重复.

        用户空间的open(),直接导致tty_fops中的tty_open()函数的执行:

    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,
    };
    
    -->

    /* BKL pushdown: scary code avoidance wrapper */
    static int tty_open(struct inode *inode, struct file *filp)
    {
    	int ret;
    
    	lock_kernel();
    	ret = __tty_open(inode, filp);
    	unlock_kernel();
    	return ret;
    }
    
    -->

    
    static int __tty_open(struct inode *inode, struct file *filp)
    {
    	... ...;
    	driver = get_tty_driver(device, &index);
    	... ...;
            tty = tty_init_dev(driver, index, 0);
    
            ... ...;
    
    	filp->private_data = tty;
            ... ...;
    		if (tty->ops->open)
    			retval = tty->ops->open(tty, filp);
    		else
    			retval = -ENODEV;
            ... ...;
    }
    
        追踪函数tty_init_driver()-->initialize_tty_struct()可以看到下面的语句:

    	tty->driver = driver;
    	tty->ops = driver->ops;
        在这里把driver(即tty_driver)赋值给tty->driver,把这个tty设备和其驱动关联起来.而driver和具体的uart_driver也是关联的.见uart_register_driver()有下面语句:

    normal->driver_state    = drv;

        回到__tty_open()函数,接下来有个判断语句:

    if (tty->ops->open)
    			retval = tty->ops->open(tty, filp);
        实际调用的是driver->ops的open函数.即又回到uart_ops:

    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,
    #ifdef CONFIG_CONSOLE_POLL
    	.poll_init	= uart_poll_init,
    	.poll_get_char	= uart_poll_get_char,
    	.poll_put_char	= uart_poll_put_char,
    #endif
    };
    
        展开uart_open函数:

    -->

    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(!kernel_locked());
    	pr_debug("uart_open(%d) called\n", line);
    
    	/*
    	 * tty->driver->num won't change, so we won't fail here with
    	 * tty->driver_data set to something non-NULL (and therefore
    	 * we won't get caught by uart_close()).
    	 */
    	retval = -ENODEV;
    	if (line >= tty->driver->num)
    		goto fail;
    
    	/*
    	 * 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(state, 0);
    
    	/*
    	 * If we succeeded, wait until the port is ready.
    	 */
    	if (retval == 0)
    		retval = uart_block_til_ready(filp, state);
    	mutex_unlock(&port->mutex);
    
    	/*
    	 * If this is the first open to succeed, adjust things to suit.
    	 */
    	if (retval == 0 && !(port->flags & ASYNC_NORMAL_ACTIVE)) {
    		set_bit(ASYNCB_NORMAL_ACTIVE, &port->flags);
    
    		uart_update_termios(state);
    	}
    
    fail:
    	return retval;
    }
    
        上面代码中,逻辑性如下:
    tty->driver_data = state
        这个tty设备的driver_data域保存了state(即上述分析的struct uart_state,见函数uart_add_one_port()).state和具体的某个uart port是关联的.这在后续的write()、read()等系统调用有很重大的意义.下面分析另外一个系统调用函数:ioctl.

        ioctl()

        比如我们对设备节点进行ioctl的TIOCMGET命令获取MODEM的状态.流程分析如下:

        首先是tty_fops相应的ioctl被调用:

    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_ioctl:

    long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
    {
    	... ...;
            case TIOCMGET:
                return tty_tiocmget(tty, file, p);
            ... ...;
     }
    
        展开tty_tiocmget()函数:

    static int tty_tiocmget(struct tty_struct *tty, struct file *file, int __user *p)
    {
    	int retval = -EINVAL;
    
    	if (tty->ops->tiocmget) {
    		retval = tty->ops->tiocmget(tty, file);
    
    		if (retval >= 0)
    			retval = put_user(retval, p);
    	}
    	return retval;
    }
        这里引发了uart_ops操作集的tiocmget函数的运行.因为tty在初始化的时候其tty->ops被初始化为uart_ops.追踪tty_init_driver()-->initialize_tty_struct()可以找到此依据.

        uart_ops:

    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,
    #ifdef CONFIG_CONSOLE_POLL
    	.poll_init	= uart_poll_init,
    	.poll_get_char	= uart_poll_get_char,
    	.poll_put_char	= uart_poll_put_char,
    #endif
    };
    
        展开uart_tiocmget函数:

    static int uart_tiocmget(struct tty_struct *tty, struct file *file)
    {
    	struct uart_state *state = tty->driver_data;
    	struct tty_port *port = &state->port;
    	struct uart_port *uport = state->uart_port;
    	int result = -EIO;
    
    	mutex_lock(&port->mutex);
    	if ((!file || !tty_hung_up_p(file)) &&
    	    !(tty->flags & (1 << TTY_IO_ERROR))) {
    		result = uport->mctrl;
    
    		spin_lock_irq(&uport->lock);
    		result |= uport->ops->get_mctrl(uport);
    		spin_unlock_irq(&uport->lock);
    	}
    	mutex_unlock(&port->mutex);
    
    	return result;
    }
    
        语句uport->ops->get_mctrl被执行.这里是指具体平台相关的uart_port操作集: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_get_mctrl函数:

    static unsigned int s3c24xx_serial_get_mctrl(struct uart_port *port)
    {
    	unsigned int umstat = rd_regb(port, S3C2410_UMSTAT);
    
    	if (umstat & S3C2410_UMSTAT_CTS)
    		return TIOCM_CAR | TIOCM_DSR | TIOCM_CTS;
    	else
    		return TIOCM_CAR | TIOCM_DSR;
    }

        匆忙略过,作笔记谨作备忘.错漏难免,多多指正.

       


       



    展开全文
  •  windows xp通用串行总线突然不可以用了,用驱动精灵和驱动人生怎么装驱动都没办法,一直冒感叹号。后来找大神问了下,在“开始菜单”-“运行”-输入命令:regedit,打开系统注册表。依次展开下面的注册表项:...
     
        windows xp通用串行总线突然不可以用了,用驱动精灵和驱动人生怎么装驱动都没办法,一直冒感叹号。后来找大神问了下,在“开始菜单”-“运行”-输入命令:regedit,打开系统注册表。依次展开下面的注册表项:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{36FC9E60-C465-11CF-8056-444553540000}
    在右面窗口找到“upperfilter”项或“lowerfilter”项,并删除,然后进入设备管理器中把通用串行总线控制器下面的所有带叹号的设备都卸载掉。在设备管理器的菜单中,点“操作”-“扫描检测硬件改动”,让系统自动重装一下驱动即可。
     
     
     
       
    展开全文
  • 这种分类方法可以将控制输入/输出设备驱动程序与其他操作系统软件分离开来。字符设备是指存取时没有缓存的设备。典型的字符设备包括鼠标、键盘、串行口等。 字符设备与块设备的主要区别是:在对字符设备发出读/写...
  • 201905一文中提到,设备管理器-其他设备-通用串行总线(USB)控制器 有黄色问号。 该文具体介绍如何消除该问号,即如何安装该驱动 双击该黄色问号,点 重新安装驱动程序 你期望向导做什么? 选择:从列表或指定...
  • 有个问题,搞了一天没解决在vs2017添加设备总是卡在这里,虚拟机串行端口已经配置了,这个是vs中的配置, vs2017+win10 ,刚开始学习,希望帮忙解决下 ![图片说明]...
  • 串行 Flash 通用驱动库 SFUD (Serial Flash Universal Driver)

    千次阅读 多人点赞 2016-07-28 08:50:43
    SFUD (Serial Flash Universal Driver) 串行 Flash 通用驱动库0、SFUD 是什么SFUD 是一款开源的串行 SPI Flash 通用驱动库。由于现有市面的串行 Flash 种类居多,各个 Flash 的规格及命令存在差异, SFUD 就是为了...
  • 驱动串行通信

    千次阅读 2004-11-27 14:05:00
    现在关注驱动和串行通信:Windows CE将设备驱动程序分为2个主要的组:本地设备驱动程序和流设备驱动程序。CE下的设备驱动程序在与应用程序相同的保护级上操作。与应用程序的差别就在于他们是DLL。了解在CE系统中加载...
  • Linux设备驱动之usb设备驱动详解

    万次阅读 2011-04-17 15:50:00
    1.Linux usb设备驱动框架USB是通用串行总线的总称,Linux内核几乎支持所有的usb设备,包括键盘,鼠标,打印机,modem,扫描仪。Linux的usb驱动分为主机驱动与gadget驱动。前者是设备连接到计算机上,通过主机驱动扫描...
  • Linux无线设备驱动
  • Linux驱动入门(一)字符设备驱动基础

    千次阅读 多人点赞 2019-08-20 21:40:29
    Linux驱动入门(一)字符设备驱动基础 Linux驱动入门系列 Linux驱动入门(一)字符设备驱动基础 Linux驱动入门(二)Led驱动 Linux驱动入门(三)轮询实现按键驱动 Linux驱动入门(四)中断实现按键驱动 Linux驱动...
  • 设备驱动程序
  • 第六章 字符设备驱动 本章导读 在整个Linux设备驱动的学习中,字符设备驱动较为基础。本章将展示Linux字符设备驱动程序的结构,并解释其主要组成部分的编程方法。 6.1节讲解了Linux字符设备驱动的关键数据结构...
  • linux设备驱动(一)

    万次阅读 2020-01-08 15:47:24
    文章目录基本概念什么是设备驱动无操作系统下的驱动有操作系统下的驱动linux设备分类字符设备块设备网络设备内核的组成源码目录结构内核主要组成部分进程调度SCHED内存管理MM虚拟文件系统VFS网络接口NET进程间通信...
  • 【STM32】STM32驱动 LCD12864程序代码(串行方式)

    万次阅读 多人点赞 2019-07-30 19:37:34
    《STM32 LCD12864 串行通信模式 (从原理让你理解)》 下方代码的实现也是基于上一篇的讲解顺序来的 设备: STM32F407ZGT6 引脚接线: VSS——GND VDD——VCC(5V or 3.3V) V0 亮度调节...
  • RT thread 设备驱动组件之USART设备

    千次阅读 2016-07-04 14:50:30
    3、了解串行设备的常见处理机制。所涉及的主要源码文件有:驱动框架文件(usart.c,usart.h),底层硬件驱动文件(serial.c,serial.h)。应用串口设备驱动时,需要在rtconfig.h中宏定义#define RT_USING_SERIAL。 ...
  • USB设备驱动概述

    千次阅读 2015-06-10 18:15:34
    USB设备驱动· 17.1 USB总线协议· 17.1.1 USB设备简介· 17.1.2 USB连接拓扑结构· 17.1.3 USB通信的流程· 17.1.4 USB四种传输模式· 17.2.1 观察USB设备的工具· 17.2.2 USB设备请求· 17.2.3 设备描述...
  • linux驱动概述和常用串行总线

    千次阅读 2009-11-12 17:56:00
    基础知识:1、没有操作系统的时候,工程师可以根据硬件设备的特点来自行定义接口,有操作系统的情况下,设备驱动的架构由操作系统定义,驱动工程师必须按照相应的架构来设计驱动。2、一个无限循环中包含了对设备驱动...
  • 学习windows驱动(WDF USB设备驱动开发)

    千次阅读 2015-10-29 18:01:04
    那么怎么进行WDF USB设备驱动开发呢? 我们需要懂什么? 需要做什么呢? 根据《竹林蹊径 深入浅出Windows驱动开发》第4章 WDF USB设备驱动开发里介绍的目录可以初见端倪,需要了解USB设备硬件结构,USB软件结构,...
  • 将STM32 USART与ChibiOS串行驱动程序配合使用  发表于 2018年7月6日 更新了 2018年8月9日 通用同步/异步接收发送器 异步模式下的串行通信是在微控制器和其他设备之间交换数据的最简单和最常用的方法之一。...
  • 1. 问题提出: 前段时间由于工作需要,安装了一些内网准入软件,这些软件由于要封电脑的U盘,后来直接导致了电脑的整个串行总线控制器的驱动文件发生了破坏,导致所有的外部设备,如USB接口,硬盘,鼠标,蓝牙等...
  • usb设备无法启动,Windows 没有启动相关的设备驱动程序 用户电脑问题: 用户电脑的usb每次只要重启就会出问题,具体就是开机后所有的usb设备us
  • 由于CC2530终端的IO端口很紧张,所以驱动12864屏幕采用串行驱动,接法很简单,只需要三根线 首先将PSB引脚接地,使12864处于串口模式;更简单的接法CS引脚都可以省略,直接将CS接到5v上,为了通信可靠,暂时保留CS...
  • 字符设备驱动(1) 成于坚持,败于止步 Linux 字符设备驱动结构  cdev 结构体  在 Linux 2.6 内核中使用 cdev 结构体描述字符设备,cdev 结构体的定义如代码所示。 1 struct cdev 2 { 3 struct kobject ...
  • 设备驱动概述(2) Linux 设备驱动 设备的分类及特点  计算机系统的硬件主要由 CPU、存储器和外设组成。随着 IC 制造工艺的发展,目前,芯片的集成度越来越高,往往在 CPU 内部就集成了存储器和外设适配器。ARM、...
  • linux驱动由浅入深系列:块设备驱动之一(高通eMMC分区实例)linux驱动由浅入深系列:块设备驱动之二(块设备驱动结构分析)块设备驱动的模型还是基本基于字符设备驱动的,可以简单理解为块设备仅仅增加了操作缓冲区...
  • USB设备驱动

    千次阅读 2013-04-10 15:24:35
    USB设备驱动 ·  17.1 USB总线协议 ·  17.1.1 USB设备简介 ·  17.1.2 USB连接拓扑结构 ·  17.1.3 USB通信的流程 ·  17.1.4 USB四种传输模式 ·  ...
  • Linux下设备驱动

    千次阅读 2013-11-14 18:02:05
    本讲主要概述Linux设备驱动框架、驱动程序的配置文件及常用的加载驱动程序的方法;并且介绍Red Hat Linux安装程序是如何加载驱动的,通过了解这个过程, 我们可以自己将驱动程序放到引导盘中;安装完系统后,使用...
  • RTThread-设备驱动

    千次阅读 2019-08-13 14:57:45
    设备驱动 1. device.c --> 设备驱动 此文件提供device相关接口文件 相关宏定义 -->rttconfig.h配置项 /* 使用标准接口 可选项 */ #define RT_USING_POSIX /* 启用设备驱动框架 必选项 */ #define ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 47,734
精华内容 19,093
关键字:

串行设备驱动是什么