Linux 串口驱动相关

Linux串口驱动相关主要涉及3个重要的结构体,uart_driver,uart_port,uart_ops。本文主要以msm8917平台分析, 先贴dts相关代码

	blsp1_uart2: serial@78b0000 {
		compatible = "qcom,msm-lsuart-v14";
		reg = <0x78b0000 0x200>;
		interrupts = <0 108 0>;
		status = "disabled";
		clocks = <&clock_gcc clk_gcc_blsp1_uart2_apps_clk>,
		<&clock_gcc clk_gcc_blsp1_ahb_clk>;
		clock-names = "core_clk", "iface_clk";
	};

几个重要结构体定义

uart_driver封装了tty_driver,使底层uart驱动不用关心tty_driver。一个tty驱动程序必须注册/注销 tty_driver,而uart驱动则变为注册/注销uart_driver。使用如下接口:

int uart_register_driver(struct uart_driver *drv);
void uart_unregister_driver(struct uart_driver *drv);

实际上,uart_register_driver()和uart_unregister_driver()中分别包含了tty_register_driver()和tty_unregister_driver()的操作。
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;
};

对应高通平台的代码

static struct uart_driver msm_hsl_uart_driver = {
	.owner = THIS_MODULE,
	.driver_name = "msm_serial_hsl",
	.dev_name = "ttyHSL",
	.nr = UART_NR,
	.cons = MSM_HSL_CONSOLE,
};

其中uart_driver中的uart_state比较重要,在uart_register_driver中为state分配了内存

/*
 * 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;

	struct uart_port	*uart_port;
};

uart_port用于描述一个UART端口(直接对应于一个串口)的I/O端口或I/O内存地址、FIFO大小、端口类型等信息。串口核心层提供如下函数来添加1个端口:

int uart_add_one_port(struct uart_driver *drv, struct uart_port *port);

对上述函数的调用应该发生在uart_register_driver()之后,uart_add_one_port()的一个最重要作用是封装了 tty_register_device()。
uart_add_one_port()的“反函数”是uart_remove_one_port(),其中会调用tty_unregister_device(),原型为:

int uart_remove_one_port(struct uart_driver *drv, struct uart_port *port);

uart_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);
	void			(*set_termios)(struct uart_port *,
				               struct ktermios *new,
				               struct ktermios *old);
	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 *);
	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 and RT288x type IO */
#define UPIO_TSI		(5)			/* Tsi108/109 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

	/* flags must be updated while holding port mutex */
	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))
/* Port has hardware-assisted h/w flow control (iow, auto-RTS *not* auto-CTS) */
#define UPF_HARD_FLOW		((__force upf_t) (1 << 21))
/* 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		((__force upf_t) (0x17fff))
#define UPF_USR_MASK		((__force upf_t) (UPF_SPD_MASK|UPF_LOW_LATENCY))

	/* status must be updated while holding port lock */
	upstat_t		status;

#define UPSTAT_CTS_ENABLE	((__force upstat_t) (1 << 0))
#define UPSTAT_DCD_ENABLE	((__force upstat_t) (1 << 1))

	int			hw_stopped;		/* sw-assisted CTS flow state */
	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		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) */
	void			*private_data;		/* generic platform data pointer */
};

对应高通平台的代码

struct msm_hsl_port {
	struct uart_port	uart;
	char			name[16];
	struct clk		*clk;
	struct clk		*pclk;
	struct dentry		*loopback_dir;
	unsigned int		imr;
	unsigned int		*uart_csr_code;
	unsigned int            *gsbi_mapbase;
	unsigned int            *mapped_gsbi;
	unsigned int            old_snap_state;
	unsigned long		ver_id;
	int			tx_timeout;
	struct mutex		clk_mutex;
	enum uart_core_type	uart_type;
	enum uart_func_mode	func_mode;
	struct wakeup_source	port_open_ws;
	int			clk_enable_count;
	u32			bus_perf_client;
	/* BLSP UART required BUS Scaling data */
	struct msm_bus_scale_pdata *bus_scale_table;
};

msm_hsl_port 对uart_port做了一个封装

static struct msm_hsl_port msm_hsl_uart_ports[] = {
	{
		.uart = {
			.iotype = UPIO_MEM,
			.ops = &msm_hsl_uart_pops,
			.flags = UPF_BOOT_AUTOCONF,
			.fifosize = 64,
			.line = 0,
		},
	},
	{
		.uart = {
			.iotype = UPIO_MEM,
			.ops = &msm_hsl_uart_pops,
			.flags = UPF_BOOT_AUTOCONF,
			.fifosize = 64,
			.line = 1,
		},
	},
	{
		.uart = {
			.iotype = UPIO_MEM,
			.ops = &msm_hsl_uart_pops,
			.flags = UPF_BOOT_AUTOCONF,
			.fifosize = 64,
			.line = 2,
		},
	},
};

uart_ops

/*
 * 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 *);
	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		(*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 *);
	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 *, int new);
	void		(*pm)(struct uart_port *, unsigned int state,
			      unsigned int oldstate);
	void		(*wake_peer)(struct uart_port *);

	/*
	 * 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
	int		(*poll_init)(struct uart_port *);
	void		(*poll_put_char)(struct uart_port *, unsigned char);
	int		(*poll_get_char)(struct uart_port *);
#endif
};

高通平台对应代码

static struct uart_ops msm_hsl_uart_pops = {
	.tx_empty = msm_hsl_tx_empty,
	.set_mctrl = msm_hsl_set_mctrl,
	.get_mctrl = msm_hsl_get_mctrl,
	.stop_tx = msm_hsl_stop_tx,
	.start_tx = msm_hsl_start_tx,
	.stop_rx = msm_hsl_stop_rx,
	.enable_ms = msm_hsl_enable_ms,
	.break_ctl = msm_hsl_break_ctl,
	.startup = msm_hsl_startup,
	.shutdown = msm_hsl_shutdown,
	.set_termios = msm_hsl_set_termios,
	.type = msm_hsl_type,
	.release_port = msm_hsl_release_port,
	.request_port = msm_hsl_request_port,
	.config_port = msm_hsl_config_port,
	.verify_port = msm_hsl_verify_port,
	.pm = msm_hsl_power,
};

驱动注册流程

在使用串口核心层这个通用串口tty驱动层的接口后,一个串口驱动要完成的主要工作将包括:

  1. 定义uart_driver、uart_ops、uart_port等结构体的实例并在适当的地方根据具体硬件和驱动的情况初始化它们,当然具体设备 xxx的驱动可以将这些结构套在新定义的xxx_uart_driver、xxx_uart_ops、xxx_uart_port之内。
  2. 在模块初始化时调用uart_register_driver()和uart_add_one_port()以注册UART驱动并添加端口,在模块卸载时调用uart_unregister_driver()和uart_remove_one_port()以注销UART驱动并移除端口。
  3. 根据具体硬件的datasheet实现uart_ops中的成员函数,这些函数的实现成为UART驱动的主体工作。

串口驱动初始化过程

在高通串口驱动的模块加载函数(msm_serial_hsl_init)中会调用uart_register_driver()注册msm_hsl_uart_driver这个uart_driver(对应driver)
同时经过msm_serial_hsl_init()–>platform_driver_register(&msm_hsl_platform_driver);–>msm_serial_hsl_probe–>初始化UART端口并调用uart_add_one_port()添加端口。(对应device)

下面开始具体分析,从uart_register_driver开始,对应代码在serial_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.                                           
     */                                                                                     
    drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);                  
    if (!drv->state)                                                                        
        goto out;                                                                           
                                                                                            
    normal = alloc_tty_driver(drv->nr);                                                     
    if (!normal)                                                                            
        goto out_kfree;                                                                     
                                                                                            
    drv->tty_driver = normal;                                                               
                                                                                            
    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);    //会调用到此ops中有uart_open等
                                                          
    /*                                                    
     * 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;                       
        port->close_delay     = HZ / 2; /* .5 seconds */  
        port->closing_wait    = 30 * HZ;/* 30 seconds */  
    }                                                     
                                                          
    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;                                       
}                                                                                                       

贴两个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,
    .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
};

static const struct tty_port_operations uart_port_ops = {
    .activate   = uart_port_activate,
    .shutdown   = uart_port_shutdown,
    .carrier_raised = uart_carrier_raised,
    .dtr_rts    = uart_dtr_rts,
};

tty_port_init代码位于tty_port.c中,在tty_port初始化中又调用到了tty_buffer_init。tty_buffer_init的代码在tty_buffer.c中
便于理解把tty_buffer_init代码贴一下

/**                                                  
 *  tty_buffer_init     -   prepare a tty buffer stru
 *  @tty: tty to initialise                          
 *                                                   
 *  Set up the initial state of the buffer management
 *  Must be called before the other tty buffer functi
 */                                                  
                                                     
void tty_buffer_init(struct tty_port *port)          
{                                                    
    struct tty_bufhead *buf = &port->buf;            
                                                     
    mutex_init(&buf->lock);                          
    tty_buffer_reset(&buf->sentinel, 0);             
    buf->head = &buf->sentinel;                      
    buf->tail = &buf->sentinel;                      
    init_llist_head(&buf->free);                     
    atomic_set(&buf->mem_used, 0);                   
    atomic_set(&buf->priority, 0);                   
    INIT_WORK(&buf->work, flush_to_ldisc);  //work比较重要         
    buf->mem_limit = TTYB_DEFAULT_MEM_LIMIT;         
}                                                    

看一下上面tty_port结构体

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 */      
    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   close_wait; /* Close waiters */                
    wait_queue_head_t   delta_msr_wait; /* Modem status change */      
    unsigned long       flags;      /* TTY flags ASY_*/                
    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 */                      
};                                                                     

再贴下tty_bufhead

struct tty_bufhead {
    struct tty_buffer *head;    /* Queue head */
    struct work_struct work;
    struct mutex       lock;
    atomic_t       priority;
    struct tty_buffer sentinel;
    struct llist_head free;     /* Free queue head */
    atomic_t       mem_used;    /* In-use buffers excluding free list */
    int        mem_limit;
    struct tty_buffer *tail;    /* Active buffer */
};

再回到tty_register_driver(normal)在tty_io.c中。
tty_cdev_add(driver, dev, 0, driver->num);会调用

static int tty_cdev_add(struct tty_driver *driver, dev_t dev,
        unsigned int index, unsigned int count)
{
    /* init here, since reused cdevs cause crashes */
    cdev_init(&driver->cdevs[index], &tty_fops);
    driver->cdevs[index].owner = driver->owner;
    return cdev_add(&driver->cdevs[index], dev, count);
}

此处关联tty_fops。用户空间会open,read,write会调用对应的tty_open,tty_read,tty_write

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

读写流程

串口打开

用户open -> tty_open -> tty_init_dev(driver, index) -> tty_ldisc_setup -> tty_ldisc_open ->ld->ops->open(tty)-> n_tty_open(n_tty.c)
当tty_init_dev走完后又会走下面的
retval = tty->ops->open(tty, filp);
tty对应tty_struct。ops->open对应driver->open对应uart_ops中的uart_open->uart_startup(tty, state, 0);->uart_port_startup->uport->ops->startup(uport)(对应msm_hsl_uart_pops)->msm_hsl_startup。至此整个流程已经走完。read,write同理分析。
其中tty_init_dev比较重要

/**                                                                                      
 *  tty_init_dev        -   initialise a tty device                                      
 *  @driver: tty driver we are opening a device on                                       
 *  @idx: device index                                                                   
 *  @ret_tty: returned tty structure                                                     
 *                                                                                       
 *  Prepare a tty device. This may not be a "new" clean device but                       
 *  could also be an active device. The pty drivers require special                      
 *  handling because of this.                                                            
 *                                                                                       
 *  Locking:                                                                             
 *      The function is called under the tty_mutex, which                                
 *  protects us from the tty struct or driver itself going away.                         
 *                                                                                       
 *  On exit the tty device has the line discipline attached and                          
 *  a reference count of 1. If a pair was created for pty/tty use                        
 *  and the other was a pty master then it too has a reference count of 1.               
 *                                                                                       
 * WSH 06/09/97: Rewritten to remove races and properly clean up after a                 
 * failed open.  The new code protects the open with a mutex, so it's                    
 * really quite straightforward.  The mutex locking can probably be                      
 * relaxed for the (most common) case of reopening a tty.                                
 */                                                                                      
                                                                                         
struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx)                      
{                                                                                        
    struct tty_struct *tty;                                                              
    int retval;                                                                          
                                                                                         
    /*                                                                                   
     * 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(driver, idx);                                                 
    if (!tty) {                                                                          
        retval = -ENOMEM;                                                                
        goto err_module_put;                                                             
    }                                                                                    
                                                                                         
    tty_lock(tty);                                                                       
    retval = tty_driver_install_tty(driver, tty);                                        
    if (retval < 0)                                                                      
        goto err_deinit_tty;       

    if (!tty->port)
        tty->port = driver->ports[idx];

    WARN_RATELIMIT(!tty->port,
            "%s: %s driver does not set tty->port. This will crash the kernel later. Fix the driver!\n",
            __func__, tty->driver->name);

    tty->port->itty = tty;

    /*
     * 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 err_release_tty;
    /* Return the tty locked so that it cannot vanish under the caller */
    return tty;

err_deinit_tty:
    tty_unlock(tty);
    deinitialize_tty_struct(tty);
    free_tty_struct(tty);
err_module_put:
    module_put(driver->owner);
    return ERR_PTR(retval);

    /* call the tty release_tty routine to clean out this slot */
err_release_tty:
    tty_unlock(tty);
    printk_ratelimited(KERN_INFO "tty_init_dev: ldisc open failed, "
                 "clearing slot %d\n", idx);
    release_tty(tty, idx);
    return ERR_PTR(retval);
}                                                      

alloc_tty_struct对tty_struct结构体进行初始化

/**
 *  alloc_tty_struct
 *
 *  This subroutine allocates and initializes a tty structure.
 *
 *  Locking: none - tty in question is not exposed at this point
 */

struct tty_struct *alloc_tty_struct(struct tty_driver *driver, int idx)
{
    struct tty_struct *tty;

    tty = kzalloc(sizeof(*tty), GFP_KERNEL);
    if (!tty)
        return NULL;

    kref_init(&tty->kref);
    tty->magic = TTY_MAGIC;
    tty_ldisc_init(tty);//设置ldisc
    tty->session = NULL;
    tty->pgrp = NULL;
    mutex_init(&tty->legacy_mutex);
    mutex_init(&tty->throttle_mutex);
    init_rwsem(&tty->termios_rwsem);
    mutex_init(&tty->winsize_mutex);
    init_ldsem(&tty->ldisc_sem);
    init_waitqueue_head(&tty->write_wait);
    init_waitqueue_head(&tty->read_wait);
    INIT_WORK(&tty->hangup_work, do_tty_hangup);
    mutex_init(&tty->atomic_write_lock);
    spin_lock_init(&tty->ctrl_lock);
    spin_lock_init(&tty->flow_lock);
    INIT_LIST_HEAD(&tty->tty_files);
    INIT_WORK(&tty->SAK_work, do_SAK_work);

    tty->driver = driver;
    tty->ops = driver->ops;//关联driver->ops
    tty->index = idx;
    tty_line_name(driver, idx, tty->name);
    tty->dev = tty_get_device(tty);

    return tty;
}

在来看下n_tty_open

/**
 *	n_tty_open		-	open an ldisc
 *	@tty: terminal to open
 *
 *	Called when this line discipline is being attached to the
 *	terminal device. Can sleep. Called serialized so that no
 *	other events will occur in parallel. No further open will occur
 *	until a close.
 */

static int n_tty_open(struct tty_struct *tty)
{
	struct n_tty_data *ldata;

	/* Currently a malloc failure here can panic */
	ldata = vmalloc(sizeof(*ldata));
	if (!ldata)
		goto err;

	ldata->overrun_time = jiffies;
	mutex_init(&ldata->atomic_read_lock);
	mutex_init(&ldata->output_lock);

	tty->disc_data = ldata;
	reset_buffer_flags(tty->disc_data);
	ldata->column = 0;
	ldata->canon_column = 0;
	ldata->minimum_to_wake = 1;
	ldata->num_overrun = 0;
	ldata->no_room = 0;
	ldata->lnext = 0;
	tty->closing = 0;
	/* indicate buffer work may resume */
	clear_bit(TTY_LDISC_HALTED, &tty->flags);
	n_tty_set_termios(tty, NULL);
	tty_unthrottle(tty);

	return 0;
err:
	return -ENOMEM;

贴下跟buf相关结构体

struct n_tty_data {
	/* producer-published */
	size_t read_head;
	size_t commit_head;
	size_t canon_head;
	size_t echo_head;
	size_t echo_commit;
	size_t echo_mark;
	DECLARE_BITMAP(char_map, 256);

	/* private to n_tty_receive_overrun (single-threaded) */
	unsigned long overrun_time;
	int num_overrun;

	/* non-atomic */
	bool no_room;

	/* must hold exclusive termios_rwsem to reset these */
	unsigned char lnext:1, erasing:1, raw:1, real_raw:1, icanon:1;
	unsigned char push:1;

	/* shared by producer and consumer */
	char read_buf[N_TTY_BUF_SIZE];
	DECLARE_BITMAP(read_flags, N_TTY_BUF_SIZE);
	unsigned char echo_buf[N_TTY_BUF_SIZE];

	int minimum_to_wake;

	/* consumer-published */
	size_t read_tail;
	size_t line_start;

	/* protected by output lock */
	unsigned int column;
	unsigned int canon_column;
	size_t echo_tail;

	struct mutex atomic_read_lock;
	struct mutex output_lock;
};

uart_port_startup中涉及到state->xmit.buf的初始化,贴下代码简单分析下

/*
 * Startup the port.  This will be called once per open.  All calls
 * will be serialised by the per-port mutex.
 */
static int uart_port_startup(struct tty_struct *tty, struct uart_state *state,
        int init_hw)
{
    struct uart_port *uport = state->uart_port;
    unsigned long page;
    int retval = 0;

    if (uport->type == PORT_UNKNOWN)
        return 1;

    /*
     * Make sure the device is in D0 state.
     */
    uart_change_pm(state, UART_PM_STATE_ON);

    /*
     * Initialise and allocate the transmit and temporary
     * buffer.
     */
    if (!state->xmit.buf) {
        /* This is protected by the per port mutex */
        page = get_zeroed_page(GFP_KERNEL);
        if (!page)
            return -ENOMEM;

        state->xmit.buf = (unsigned char *) page;
        uart_circ_clear(&state->xmit);
    }

    retval = uport->ops->startup(uport);
    if (retval == 0) {
        if (uart_console(uport) && uport->cons->cflag) {
            tty->termios.c_cflag = uport->cons->cflag;
            uport->cons->cflag = 0;
        }
        /*
         * Initialise the hardware port settings.
         */
        uart_change_speed(tty, state, NULL);

        if (init_hw) {
            /*
             * Setup the RTS and DTR signals once the
             * port is open and ready to respond.
             */
            if (tty->termios.c_cflag & CBAUD)
                uart_set_mctrl(uport, TIOCM_RTS | TIOCM_DTR);
        }

        spin_lock_irq(&uport->lock);
        if (uart_cts_enabled(uport) &&
            !(uport->ops->get_mctrl(uport) & TIOCM_CTS))
            uport->hw_stopped = 1;
        else
            uport->hw_stopped = 0;
        spin_unlock_irq(&uport->lock);
    }

    /*
     * This is to allow setserial on this port. People may want to set
     * port/irq/type and then reconfigure the port properly if it failed
     * now.
     */
    if (retval && capable(CAP_SYS_ADMIN))
        return 1;

    return retval;
}

tty_open 的工作:

  1. 获取到 tty_driver
  2. 根据 tty_driver 初始化一个 tty_struct
    2.1 设置 tty_struct 的线路规程为 N_TTY (不同类型的线路规程有不同的 ops)
    2.2 初始化一个延时工作队列,唤醒时调用flush_to_ldisc ,读函数时我们需要分析它。
    2.3 初始化 tty_struct 里的两个等待队列头。
    2.4 设置 tty_struct->ops == tty_driver->ops 。
  3. 在 tty_ldisc_setup 函数中调用到线路规程的open函数,对于 N_TTY 来说是 n_tty_open 。
  4. 如果 tty_struct->ops 也就是 tty_driver->ops 定义了 open 函数则调用,显然是有的 uart_open 。

串口写流程

tty_write–>do_tty_write(ld->ops->write, tty, file, buf, count)(把用户空间的buf拷贝到tty->write_buf)–>n_tty_write–>tty->ops->write(tty, b, nr)–>uart_write(把数据拷贝到state->xmit环形缓冲区中)–>uart_start(tty)–>__uart_start(tty)–>port->ops->start_tx(port)–>msm_hs_start_tx
写流程:

  1. 将当前进程加入到等待队列
  2. 设置当前进程为可打断的
  3. 层层调用最终调用到底层的 start_tx 函数,将要发送的数据存入 DATA 寄存器,由硬件自动发送。
  4. 进程调度,当前进程进入休眠。
  5. 硬件发送完成,进入中断处理函数,唤醒对面队列。

串口读流程

tty_read–>ld->ops->read(tty, file, buf, count)–>n_tty_read–>copy_from_read_buf(tty, &b, &nr)–>copy_to_user(*b, read_buf_add(ldata, tail), n)(把ldata=tty->disc_data中的read_buf拷贝到用户空间)
再来看下ldata从哪里来
原始数据肯定是硬件读出来的。
msm_hsl_irq–>handle_rx(port, misr)–>tty_flip_buffer_push–>tty_schedule_flip–>schedle_work(&buf->work)–>flush_to_ldisc(操作port->buf)–>receive_buf(tty, head, count)–>disc->ops-receive_buf2–>n_tty_receive_buf2–>n_tty_receive_buf_common–>__receive_buf(tty, cp, fp, n)–>n_tty_receive_buf_raw–>put_tty_queue(*cp++, ldata)–>*read_buf_addr(ldata, ldata->read_head) = c–>&ldata->read_buf[i & (N_TTY_BUF_SIZE - 1)]

主要工作

  1. 将当前进程加入等待队列
  2. 设置当前进程可中断
  3. 进程调度,当前进程进入休眠
  4. 在某处被唤醒
  5. 从 tty->read_buf 取出数据,通过 tty_put_user 拷贝到用户空间。
    唤醒是在中断处理函数中,当DATA寄存器满,触发中断,中断处理函数中调用 tty_flip_buffer_push 。

几个重点问题

环形缓冲区

线路规程
注册在console_init(tty_io.c)–>tty_ldisc_begin()–> (void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);

猜你喜欢

转载自blog.csdn.net/vv_cool/article/details/83507003