Linux系统编程@终端IO-程序员宅基地

技术标签: 操作系统  

Linux系统中终端设备种类 

终端是一种字符型设备,有多种类型,通常使用tty 来简称各种类型的终端设备。终端特殊设备文件一般有以下几种: 串行端口终端/dev/ttySn ) ,伪终端/dev/pty/ ),控制终端/dev/tty ) ,控制台终端/dev/ttyn, /dev/console )。

1. 串行端口终端(Serial Port Terminal )是使用计算机串行端口连接的终端设备。Linux中每个串行端口都看作是一个字符设备。串行端口所对应的设备名称是/dev/tts/0 (或/dev/ttyS0 )、/dev/tts/1 (或/dev/ttyS1 )等,设备号分别是(4,0 )、(4,1 )等,分别对应于DOS 系统下的COM1 、COM2 等。

2. 伪终端(Pseudo Terminal )是成对的逻辑终端设备,例如/dev/ptyp3 和/dev/ttyp3 (在设备文件系统中分别是/dev/pty/m3 和/dev/pty/s3 ), 它们与实际物理设备并不直接相关。对于ttyp3 (s3 ),任何设计成使用一个串行端口设备的程序都可以使用该逻辑设备。但对于使用ptyp3 的程序,则需要专门设计来使用ptyp3 (m3 )逻辑设备。 如果使用telnet 远程登录,则telnet 会开始连接到设备ptyp2 (m2 )上(一个伪终端端口上)。此时一个getty 程序就应该运行在对应的ttyp2 (s2 )端口上。当telnet 从远端获取了一个字符时,该字符就会通过m2 、s2 传递给getty 程序,而getty 程序就会通过s2 、m2 和telnet 程序往网络上返回”login: ”字符串信息。登录程序与telnet 程序就通过“伪终端”进行通信,通过使用适当的软件,就可以把两个甚至多个伪终端设备连接到同一个物理串行端口上。因为只存在16个ttyp (ttyp0 —ttypf )的设备文件,就使用了象q 、r 、s 等字符来代替p 。例如,ttys8 和ptys8 就是一个伪终端设备对。目录/dev/pts 是一个类型为devpts 的文件系统,并且可以在被加载文件系统列表中看到。虽然/dev/pts 象是设备文件系统中的一项,但它其实是一种不同的文件系统。

3. 如果当前进程有控制终端(Controlling Terminal )的话,那么/dev/tty 就是当前进程的控制终端的设备特殊文件。可以使用命令”ps –ax ”来查看进程与哪个控制终端相连使用命令”tty ”可以查看它具体对应哪个实际终端设备。/dev/tty 有些类似于到实际所使用终端设备的一个联接。

4.  Linux 系统中,计算机显示器通常被称为控制台终端(Console ), 它仿真了类型为Linux 的一种终端(TERM=Linux ),并且有一些设备特殊文件与之相关联:tty0 、tty1 、tty2 等。当你在控制台上登录时,使用的是tty1 。使用Alt+[F1 —F6] 组合键时,我们就可以切换到tty2 、tty3 等上面去。tty1 –tty6 等称为虚拟终端,而tty0则是当前所使用虚拟终端的一个别名,系统所产生的信息会发送到该终端上。只有系统或超级用户root 可以向/dev/tty0进行写操作 .

 

 


 

终端的体系结构                带线路规程的串口驱动数据流向

tty核心:(系统实现)

tty线路规程:(自己实现,可有可无)实现规范或协议,通过注册实现。(在驱动中使用相关协议都需要注册么?USB、I2C、SPI)

tty驱动:(自己实现)

ttyS0:串口设备

tty_io.c:属于tty核心部分

n_tty.c:线路规程部分

 

串口驱动数据流与函数调用

串口驱动中并不像一般的字符驱动在函数指针中实现了读的操作,串口设备(Hardware层)接收到数据后产生中断,然后将数据传入到线路规程中存储起来,当用户层需要读取数据时再去线路规程中去取数据。这个过程中TTY核心将从TTY驱动收到的数据缓存到一个 tty_flip_buffer类型的结构中。该结构包含两个数据数组。从TTY设备接收到的数据被存储于第一个数组,当这个数组满时,等待数据的用户将被通知。当用户从这个数组中读数据时,任何从TTY驱动新来的数据将被存储在第2个数组。然后交替使用。

串口驱动的描述

 

struct uart_driver {                 //include\linux\Serial_core.h
	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;
};

 

串口驱动的注册

int uart_register_driver(struct uart_driver *uart);

UART端口描述

uart_port用于描述一个UART端口(一个串口)的地址、FIFO大小、端口类型等信息。

struct uart_port {
	spinlock_t		lock;			/* port lock 端口锁*/
	unsigned long		iobase;			/* in/out[bwl] IO端口基地址*/
	unsigned char __iomem	*membase;		/* read/write[bwl]IO内存基地址 */
	unsigned int		(*serial_in)(struct uart_port *, int);
	void			(*serial_out)(struct uart_port *, int, int);
	unsigned int		irq;			/* irq number 中断号*/
	unsigned int		uartclk;		/* base uart clock */
	unsigned int		fifosize;		/* tx fifo size 传输fifo大小*/
	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_info	*info;			/* pointer to parent info */
	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_ops定义了针对串口的一系列操作,包括发送、接收及线路设置等。

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);  //发送xchar
	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
};

添加端口

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

串口驱动实现操作流程

  1. 定义一个uart_driver的变量,并初始化;
  2. 使用uart_register_driver来注册这个驱动;
  3. 初始化uart_port和ops函数表;
  4. 调用uart_add_one_port()添加初始化好的uart_port。

串口驱动代码实例分析

发送和接收的原理

发送:循环buffer ->发送fifo->发送移位寄存器

接收:接收移位寄存器à接收fifo ->Flip_buf

发送的过程是:把数据写到发送fifo中,fifo把收到的数据传给发送移位寄存器(自动的,非driver控制),然后每个时钟脉冲往串口线上写一bit数据。

接收的过程是:接收移位寄存器收到数据,发送给接收fifo,接收fifo事先设置好了触发门限(寄存器中设置),当里面的数据量超过门限时就会触发一个中断,调用驱动中的中断处理函数,把数据写到flip_buf中。

 

基于2.4内核的代码实现

2.4与2.6内核串口驱动的唯一区别是2.4中port直接作为struct uart_driver的成员,但是2.6中不是直接作为其成员,而是使用uart_add_one_port()函数将uart_driver与port关联起来。

模块初始化函数

static int __init s3c2410uart_init(void)
{
    return uart_register_driver(&s3c2410_reg);
}

使用uart_register_driver注册串口驱动。注册前需要实现串口驱动的结构s3c2410_reg;如下

static struct uart_driver s3c2410_reg = {
	owner:		THIS_MODULE,
	normal_major:	SERIAL_S3C2410_MAJOR,
	normal_name:	"ttyS%d",
	callout_name:	"cua%d",
	normal_driver:	&normal,
	callout_major:	CALLOUT_S3C2410_MAJOR,
	callout_driver:	&callout,
	table:		s3c2410_table,
	termios:	s3c2410_termios,
	termios_locked:	s3c2410_termios_locked,
	minor:		MINOR_START,
	nr:		UART_NR,
	port:		s3c2410_ports,  //最重要的成员
	cons:		S3C2410_CONSOLE,
};

static struct uart_port	s3c2410_ports[UART_NR] = {
  {
	iobase:		(unsigned long)(UART0_CTL_BASE), //IO基地址
	iotype:		SERIAL_IO_PORT,           //类型IO
	irq:		IRQ_RXD0,               //中断
	uartclk:	130252800,              //时钟
	fifosize:	16,                  //fifo大小
	ops:		&s3c2410_pops,            //操作函数指针(最核心部分)
	type:		PORT_S3C2410,
	flags:		ASYNC_BOOT_AUTOCONF,
  }, 
。。。。。。     。。。。。。。      。。。。。。。
};

static struct uart_ops s3c2410_pops = {
        tx_empty:		s3c2410uart_tx_empty,
     set_mctrl:	s3c2410uart_set_mctrl,
	get_mctrl:	s3c2410uart_get_mctrl,
	stop_tx:		s3c2410uart_stop_tx,
	start_tx:		s3c2410uart_start_tx,
	stop_rx:		s3c2410uart_stop_rx,
	enable_ms:	s3c2410uart_enable_ms,
	break_ctl:	s3c2410uart_break_ctl,
	startup:		s3c2410uart_startup,
	shutdown:		s3c2410uart_shutdown,
	change_speed:	s3c2410uart_change_speed,
	type:		s3c2410uart_type,
	config_port:	s3c2410uart_config_port,
	release_port:	s3c2410uart_release_port,
	request_port:	s3c2410uart_request_port,
};

 

部分操作函数实现

阻止发送函数uart_stop_tx 

static void s3c2410uart_stop_tx(struct uart_port *port, u_int from_tty)
{ 
  disable_irq(TX_IRQ(port)); 
} 
//停止发送的功能,其内部的函数disable_irq是停止中断的功能 ,发送数据是通过中断来完成的,关闭中断也就关闭了发送。

发送使能函数uart_start_tx

static void s3c2410uart_start_tx(struct uart_port *port, u_int nonempty, u_int from_tty) 
{ 
    enable_irq(TX_IRQ(port)); 
} 
//与上面的过程类似,就是一个相反的过程 

阻止接收函数uart_stop_rx 

static void s3c2410uart_stop_rx(struct uart_port *port) 
{ 
    disable_irq(RX_IRQ(port)); 
} 
//阻止接收的原理与阻止发送的原理是一样的

发送缓冲空判断函数uart_tx_empty

static u_int s3c2410uart_tx_empty(struct uart_port *port) 
{ 
    return (UART_UTRSTAT(port) & UTRSTAT_TR_EMP ? 0 : TIOCSER_TEMT); 
} 

//如果发送缓冲为空则返回0,否则返回1。
//在寄存器UTRSTATn [2]标志了缓存是否为空,UART_UTRSTAT(port) & UTRSTAT_TR_EMP便是对应着这一位的值。

获取控制信息函数uart_get_mctrl 

static u_int s3c2410uart_get_mctrl(struct uart_port *port) 
{ 
  return (TIOCM_CTS | TIOCM_DSR | TIOCM_CAR); 
} 
//获得控制信息, TIOCM_CTS ,TIOCM_DSR 和TIOCM_CAR,这几个宏代表串口的控制信息, 分别是clear to send,data set ready和data carrier detect(详见Serial Programming Guide for POSIX Operating Systems) 

接收中断函数uart_rx_interrupt 

static void s3c2410uart_rx_interrupt(int irq, void *dev_id, struct pt_regs *regs) 
{ 
  struct uart_info *info = dev_id; 
  struct tty_struct *tty = info->tty; 
  unsigned int status, ch, max_count = 256; 
  struct uart_port *port = info->port; 

  status = UART_UTRSTAT(port); 
  while ((status & UTRSTAT_RX_RDY) && max_count--)  //一个字节一个字节的发送,直达全部字节(每次最多处理man_count个字节)发送完毕
  { 
    if (tty->flip.count >= TTY_FLIPBUF_SIZE)   //交替使用buffer
    { 
      tty->flip.tqueue.routine((void *) tty);   //交换操作

      if (tty->flip.count >= TTY_FLIPBUF_SIZE) { 
        printk(KERN_WARNING "TTY_DONT_FLIP set\n"); 
        return; 
      } 
    } 
    ch = UART_URXH(port); 
    *tty->flip.char_buf_ptr = ch;          //将从串口接收到的数据(一个字节)存到flip buffer中
    *tty->flip.flag_buf_ptr = TTY_NORMAL;     
    port->icount.rx++; 
    tty->flip.flag_buf_ptr++; 
    tty->flip.char_buf_ptr++; 
    tty->flip.count++; 
   
    status = UART_UTRSTAT(port); 
  } 
  tty_flip_buffer_push(tty); 
  return; 
} 

/**********************

功能:主要是是while大循环,首先看循环判断条件status & UTRSTAT_RX_RDY,前面有status = UART_UTRSTAT(port),查2410的datasheet, status & UTRSTAT_RX_RDY这个位是判断接收buffer内是否还有有效数据?按道理一次中断只是把接收的fifobuffer中的数据放到flipbuffer中去,接收的fifo的中断门限是4-12字节,进行一次接收往往要中断好多次,这样中断开销比较大,所以在while的循环条件中判断一下是否还有接收的有效数据,如果有,就继续在中断程序中继续接收,当然,永远都在接收中断中(如果一直有数据要接收)也不合适,所以while循环还有计数,最多循环256次。

在循环中,首先是要判断一下接收数据用的flip-buffer是不是已经满了, if (tty->flip.count >= TTY_FLIPBUF_SIZE)如果满了,就要跳到另一个buffer上去, tty->flip.tqueue.routine((void *) tty)是用来实现跳到另一个buffer上的功能,然后把收到的数据写到flip-buffer中,相应的状态,统计数据都要改,接着再来while 循环,循环结束后就要调用tty_flip_buffer_push(tty)来让用户把存在缓冲里的数据取走,接收一次都要把缓存清空。

*********************/

 

发送中断函数uart_tx_interrupt 

static void s3c2410uart_tx_interrupt(int irq, void *dev_id, struct pt_regs *reg) { 
  struct uart_info *info = dev_id; 
  struct uart_port *port = info->port; 
  int count; 

  if (port->x_char) {             //x_char停止位
    UART_UTXH(port) = port->x_char; 
    port->icount.tx++; 
    port->x_char = 0; 
    return; 
  } 

  if (info->xmit.head == info->xmit.tail || info->tty->stopped   //循环buffer是否没数据要发送
                          || info->tty->hw_stopped) {     s3c2410uart_stop_tx(info->port, 0);                 //没数据的话关闭发送,否则可能产生中断     return;   }   count = port->fifosize >> 1;                     //否则 16/2,即每次操作两组fifo中的一组   do {     UART_UTXH(port) = info->xmit.buf[info->xmit.tail];     info->xmit.tail = (info->xmit.tail + 1) & (UART_XMIT_SIZE - 1);     port->icount.tx++;     if (info->xmit.head == info->xmit.tail)       break;   } while (--count > 0);   if (CIRC_CNT(info->xmit.head, info->xmit.tail, UART_XMIT_SIZE) < WAKEUP_CHARS)     uart_event(info, EVT_WRITE_WAKEUP);           //通知tty core和上层,fifo中有空间可以存储数据了   if (info->xmit.head == info->xmit.tail)     s3c2410uart_stop_tx(info->port, 0); }

/*

        (1) 首先查看port中的x_char是不是为0,不为0则把x_char发送出去。x_char是xon/xoff的意思,每发一个字节时在开始前先发xon信号,在结束时发xoff。

        (2) 如果x_char没有被设置,再看环形缓冲区是否为空,或者info->tty->stopped 和 info->tty->hw_stopped 两个位是不是为1,如果这些条件成立的话,就停止发送。Tty->stop指示tty设备是否停止,tty->hw_stop指示tty设备的硬件是否停止了,以上两个位都可以通过ttydriver来设定,否则的话说明有数据要发送。

        (3) 如果以上条件都通过了,就利用一个while循环正式发送数据了,从环形缓冲尾巴上取一个数赋给UART_UTXH(port)(发送FIFO), UART_UTXH(port) = info->xmit.buf[info->xmit.tail],这条语句就是把数据送到发送FIFO中,然后计数++,循环一共进行fifosize/2次,也就是一次只能发送8 byte。

        (4)循环传送完一次后,再查看缓冲器里还剩余多少数据,如果少于WAKEUP_CHARS(256)的话,就执行uart_event(info, 0),告诉TTY核心,可以接受更多数据了。这里可以看出,tty_driver和tty_core之间的层次,tty_driver可以知道缓冲空还是满,但是它没有权力让发送数据过来,它只能是通知tty_core,让它来处理。

        (5) 最后再察看一下环形寄存器,如果serial core 没有发送来更多的数据,就关闭发送。

*/

  

出错中断函数uart_err_interrupt

static void s3c2410uart_err_interrupt(int irq, void *dev_id, 
                       struct pt_regs *reg) { 
  struct uart_info *info = dev_id; 
  struct uart_port *port = info->port;  

  struct tty_struct *tty = info->tty; 
  unsigned char err = UART_UERSTAT(port) & UART_ERR_MASK; 
  unsigned int ch, flg; 
  ch = UART_URXH(port); 
  if (!(err & (UERSTAT_BRK | UERSTAT_FRAME | 
      UERSTAT_PARITY | UERSTAT_OVERRUN))) 
    return; 
  if (err & UERSTAT_BRK) 
    port->icount.brk++; 
  if (err & UERSTAT_FRAME) 
    port->icount.frame++; 
  if (err & UERSTAT_PARITY) 
    port->icount.parity++; 
  if (err & UERSTAT_OVERRUN) 
    port->icount.overrun++; 
  err &= port->read_status_mask; 
  if (err & UERSTAT_PARITY) 
    flg = TTY_PARITY; 
  else if (err & UERSTAT_FRAME) 
    flg = TTY_FRAME; 
  else 
    flg = TTY_NORMAL; 
  if (err & UERSTAT_OVERRUN) { 
    *tty->flip.char_buf_ptr = ch; 
    *tty->flip.flag_buf_ptr = flg; 
    tty->flip.flag_buf_ptr++; 
    tty->flip.char_buf_ptr++; 
    tty->flip.count++; 
    if (tty->flip.count < TTY_FLIPBUF_SIZE) { 
      ch = 0; 
      flg = TTY_OVERRUN; 
    } 
  } 
  *tty->flip.flag_buf_ptr++ = flg; 
  *tty->flip.char_buf_ptr++ = ch; 
  tty->flip.count++; 
} 
#endif  

/*

首先err = UART_UERSTAT(port) & UART_ERR_MASK确定了err的值,err的值是从是从UART Error Status Register读到的,该erstate只用了四位,所以用UART_ERR_MASK把高四位掩掉,然后测试低四位中哪个位被置1了,从而判断错误种类UERSTAT_BRK/FRAME/PARITY/OVERRUN 分别代表1000/0100/0010/0001 ,判断出错误种类再进行相应的中断计数,然后再根据不同的err给flg设上不同的值,有

#define TTY_NORMAL 0

#define TTY_BREAK 1

#define TTY_FRAME 2

#define TTY_PARITY 3

#define TTY_OVERRUN 4 

*/

  

 

初始化函数uart_startup

 

static int s3c2410uart_startup(struct uart_port *port, struct uart_info *info) 
{ 
  int ret, flags; 
  u_int ucon; 
  ret = request_irq(RX_IRQ(port), s3c2410uart_rx_interrupt, SA_INTERRUPT, 
    "serial_s3c2410_rx", info); 
  if (ret) goto rx_failed; 
  ret = request_irq(TX_IRQ(port), s3c2410uart_tx_interrupt, SA_INTERRUPT, 
    "serial_s3c2410_tx", info); 
  if (ret) goto tx_failed; 
#ifdef CONFIG_USE_ERR_IRQ 
  ret = request_irq(ERR_IRQ(port), s3c2410uart_err_interrupt, SA_INTERRUPT, 
    "serial_s3c2410_err", info); 
  if (ret) goto err_failed; 
#endif 
  ucon = (UCON_TX_INT_LVL | UCON_RX_INT_LVL | 
       UCON_TX_INT | UCON_RX_INT | UCON_RX_TIMEOUT); 
#if defined(CONFIG_IRDA) || defined(CONFIG_IRDA_MODULE) 
  ULCON2 |= ULCON_IR | ULCON_PAR_NONE | ULCON_WL8 | ULCON_ONE_STOP; 
#endif  

  save_flags(flags); 
  cli(); 
  UART_UCON(port) = ucon; 
  sti(); 
  restore_flags(flags); 
  return 0; 
#ifdef CONFIG_USE_ERR_IRQ 
  err_failed: 
  free_irq(TX_IRQ(port), info); 
#endif 
  tx_failed: 
  free_irq(RX_IRQ(port), info); 
  rx_failed: 
  return ret; 
} 

/*

如果使用了函数open(ttyS0),那么最后调用的实现open功能的就是这个函数,它打开ttyS0。

1:利用request_irq()申请发送,接收,错误三个中断,如果失败,就要释放调已经申请的全部资源 

2:设置UART Control Register

*/

 

函数uart_change_speed

static void s3c2410uart_change_speed(struct uart_port *port, u_int cflag, u_int iflag, u                      _int quot) 
{ 
  u_int ulcon, ufcon;  

  int flags; 
  ufcon = UART_UFCON(port); 
  switch (cflag & CSIZE) { 
    case CS5: ulcon = ULCON_WL5; break; 
    case CS6: ulcon = ULCON_WL6; break; 
    case CS7: ulcon = ULCON_WL7; break; 
    default: ulcon = ULCON_WL8; break; 
  } 
  if (cflag & CSTOPB) 
  ulcon |= ULCON_STOP; 
  if (cflag & PARENB) { 
    if (!(cflag & PARODD)) 
    ulcon |= ULCON_PAR_EVEN; 
  } 
  if (port->fifosize > 1) 
    ufcon |= UFCON_FIFO_EN; 
  port->read_status_mask = UERSTAT_OVERRUN; 
  if (iflag & INPCK) 
    port->read_status_mask |= UERSTAT_PARITY | UERSTAT_FRAME; 
    port->ignore_status_mask = 0; 
  if (iflag & IGNPAR) 
    port->ignore_status_mask |= UERSTAT_FRAME | UERSTAT_PARITY; 
  if (iflag & IGNBRK) { 
    if (iflag & IGNPAR) 
      port->ignore_status_mask |= UERSTAT_OVERRUN; 
  } 
  quot -= 1; 
  save_flags(flags); 
  cli(); 
  UART_UFCON(port) = ufcon; 
  UART_ULCON(port) = (UART_ULCON(port) & ~(ULCON_PAR | ULCON_WL)) | ulcon; 
  UART_UBRDIV(port) = quot; 
  sti(); 
  restore_flags(flags); 
} 

/*

1:

UBRDIVn=(int)(CLK/(bps*16))-1

quot=(CLK / (baudrate x 16) ) (CLK为PCLK或UCLK,baudrate的单位是bps

(1):首先看一下cflag的cs位,同CS5/6/7比较,然后设置ulcon,接下来的几个if也是将ulcon根据cflag的设置进行一下设置,设置了停止位,校验位。

(2):如果port中设置了fifosize,就把UFCON(物理地址0x50000008)的第0位设为1。

*/

  

控制台

注册控制台 

void __init s3c2410_console_init(void) 
{ 
  register_console(&s3c2410_cons); 
} 

static struct console s3c2410_cons = {
	name:		"ttyS",
	write:		s3c2410_console_write,
	device:		s3c2410_console_device,
	wait_key:	s3c2410_console_wait_key,
	setup:		s3c2410_console_setup,
	flags:		CON_PRINTBUFFER,
	index:		-1,
};

  

函数console_write

static void s3c2410_console_write(struct console *co, const char *s, u_int count) 
{ 
  int i; 
  struct uart_port *port = s3c2410_ports + co->index; 
  for (i = 0; i < count; i++) { 
    while (!(UART_UTRSTAT(port) & UTRSTAT_TX_EMP)); 
    UART_UTXH(port) = s[i]; 
    if (s[i] == '\n') { 
      while (!(UART_UTRSTAT(port) & UTRSTAT_TX_EMP)); 
      UART_UTXH(port) = '\r'; 
    } 
  } 
} 

/*

通过串口往外发送数据

for循环count次,每次发送一个字符,当发送缓冲寄存器为空时,就往里写一个字符,如果写的数据是回车加换行,就要再写一个换行符

*/

  

函数console_waitkey

static int s3c2410_console_wait_key(struct console *co) 
{ 
    int c; 
    struct uart_port *port = s3c2410_ports + co->index; 
    while (!(UART_UTRSTAT(port) & UTRSTAT_RX_RDY)); 
    c = UART_URXH(port);  

    return c; 
} 

/*

该函数在while循环中等待接收数据,一旦接收缓冲器中有有效数据,该函数立即返回,返回值为接收到的一个字符

*/

  

 

函数console_device

static kdev_t s3c2410_console_device(struct console *co) 
{ 
    return MKDEV(SERIAL_S3C2410_MAJOR, MINOR_START + co->index); 
} 

/*

通过主,次设备号返回kdev_t结构

*/

  

设置函数console_setup 

static int __init s3c2410_console_setup(struct console *co, char *options) 
{ 
  struct uart_port *port; 
  int baud = 115200; 
  int bits = 8; 
  int parity = 'n'; 
  int flow = 'n'; 
  port = uart_get_console(s3c2410_ports, UART_NR, co); 
  if (options) 
    uart_parse_options(options, &baud, &parity, &bits, &flow); 
  return uart_set_options(port, co, baud, parity, bits, flow); 
} 

/*

这个函数就是设置控制台(console)的状态,里面主要有三个函数

(1)uart_get_console (struct uart_port *ports, int nr, struct console *co)

该函数检查是否co->index是一个无效的index number,返回指向该index number 对应的uart_port struct的指针

(2)uart_parse_options (options, &baud, &parity, &bits, &flow)

如果option被设置了,那么就要调用该函数,该函数解析options,options来自上一层函数的参数,它是一个字符串,应该包含baudrate,parity,bits,flow这些信息。

(3)uart_set_options( port, co, baud, parity, bits, flow)

针对以上给定的信息,对console的cflag进行设置.还要调用一下ops中的change_speed对baud rate进行实际的设置(物理),成功地话return 0

*/

 

 

 

 


 

终端控制

Linux提供了命令或相关函数对终端进行控制,已达到用户对输出显示或终端的特定要求。

终端控制命令

$ stty #显示终端的相关信息

stty命令可以直接修改和查询终端驱动程序的设置。

终端控制函数

获取文件描述符是否是某个终端设备

 

#include <unistd.h>
int isatty(int filedes) ;
                                         //返回:若为终端设备则为1(真),否则为0(假)
char *ttyname(int filedes) ;
                                         //返回:指向终端路径名的指针,若出错则为NULL 
                         //错误信息:EBADF:非法文件描述符 ENOTTY:该文件描述符非终端描述符

 

函数tcgetattr和tcsetattr可以获得或设置termios。

struct termios{
    tcflag_t c_iflag;
    tcflag_t c_oflag;
    tcflag_t c_cflag;
    tcflag_t c_lfalg;
    cc_t c_cc[NCCS];  //控制字符,用于保存终端驱动程序中的特殊字符。
};

 

#include <termios.h>
int tcgetattr(int filedes, struct termios* termptr) ;
int tcsetattr(int filedes, int opt, const struct termios* termptr) ;
                                                           //两个函数返回:若成功则为0,若出错则为- 1            

 

终端标志

 

 

 To be continue...

转载于:https://www.cnblogs.com/kwseeker-bolgs/p/4430678.html

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_30787531/article/details/97575180

智能推荐

使用nginx解决浏览器跨域问题_nginx不停的xhr-程序员宅基地

文章浏览阅读1k次。通过使用ajax方法跨域请求是浏览器所不允许的,浏览器出于安全考虑是禁止的。警告信息如下:不过jQuery对跨域问题也有解决方案,使用jsonp的方式解决,方法如下:$.ajax({ async:false, url: 'http://www.mysite.com/demo.do', // 跨域URL ty..._nginx不停的xhr

在 Oracle 中配置 extproc 以访问 ST_Geometry-程序员宅基地

文章浏览阅读2k次。关于在 Oracle 中配置 extproc 以访问 ST_Geometry,也就是我们所说的 使用空间SQL 的方法,官方文档链接如下。http://desktop.arcgis.com/zh-cn/arcmap/latest/manage-data/gdbs-in-oracle/configure-oracle-extproc.htm其实简单总结一下,主要就分为以下几个步骤。..._extproc

Linux C++ gbk转为utf-8_linux c++ gbk->utf8-程序员宅基地

文章浏览阅读1.5w次。linux下没有上面的两个函数,需要使用函数 mbstowcs和wcstombsmbstowcs将多字节编码转换为宽字节编码wcstombs将宽字节编码转换为多字节编码这两个函数,转换过程中受到系统编码类型的影响,需要通过设置来设定转换前和转换后的编码类型。通过函数setlocale进行系统编码的设置。linux下输入命名locale -a查看系统支持的编码_linux c++ gbk->utf8

IMP-00009: 导出文件异常结束-程序员宅基地

文章浏览阅读750次。今天准备从生产库向测试库进行数据导入,结果在imp导入的时候遇到“ IMP-00009:导出文件异常结束” 错误,google一下,发现可能有如下原因导致imp的数据太大,没有写buffer和commit两个数据库字符集不同从低版本exp的dmp文件,向高版本imp导出的dmp文件出错传输dmp文件时,文件损坏解决办法:imp时指定..._imp-00009导出文件异常结束

python程序员需要深入掌握的技能_Python用数据说明程序员需要掌握的技能-程序员宅基地

文章浏览阅读143次。当下是一个大数据的时代,各个行业都离不开数据的支持。因此,网络爬虫就应运而生。网络爬虫当下最为火热的是Python,Python开发爬虫相对简单,而且功能库相当完善,力压众多开发语言。本次教程我们爬取前程无忧的招聘信息来分析Python程序员需要掌握那些编程技术。首先在谷歌浏览器打开前程无忧的首页,按F12打开浏览器的开发者工具。浏览器开发者工具是用于捕捉网站的请求信息,通过分析请求信息可以了解请..._初级python程序员能力要求

Spring @Service生成bean名称的规则(当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致)_@service beanname-程序员宅基地

文章浏览阅读7.6k次,点赞2次,收藏6次。@Service标注的bean,类名:ABDemoService查看源码后发现,原来是经过一个特殊处理:当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致public class AnnotationBeanNameGenerator implements BeanNameGenerator { private static final String C..._@service beanname

随便推点

二叉树的各种创建方法_二叉树的建立-程序员宅基地

文章浏览阅读6.9w次,点赞73次,收藏463次。1.前序创建#include&lt;stdio.h&gt;#include&lt;string.h&gt;#include&lt;stdlib.h&gt;#include&lt;malloc.h&gt;#include&lt;iostream&gt;#include&lt;stack&gt;#include&lt;queue&gt;using namespace std;typed_二叉树的建立

解决asp.net导出excel时中文文件名乱码_asp.net utf8 导出中文字符乱码-程序员宅基地

文章浏览阅读7.1k次。在Asp.net上使用Excel导出功能,如果文件名出现中文,便会以乱码视之。 解决方法: fileName = HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8);_asp.net utf8 导出中文字符乱码

笔记-编译原理-实验一-词法分析器设计_对pl/0作以下修改扩充。增加单词-程序员宅基地

文章浏览阅读2.1k次,点赞4次,收藏23次。第一次实验 词法分析实验报告设计思想词法分析的主要任务是根据文法的词汇表以及对应约定的编码进行一定的识别,找出文件中所有的合法的单词,并给出一定的信息作为最后的结果,用于后续语法分析程序的使用;本实验针对 PL/0 语言 的文法、词汇表编写一个词法分析程序,对于每个单词根据词汇表输出: (单词种类, 单词的值) 二元对。词汇表:种别编码单词符号助记符0beginb..._对pl/0作以下修改扩充。增加单词

android adb shell 权限,android adb shell权限被拒绝-程序员宅基地

文章浏览阅读773次。我在使用adb.exe时遇到了麻烦.我想使用与bash相同的adb.exe shell提示符,所以我决定更改默认的bash二进制文件(当然二进制文件是交叉编译的,一切都很完美)更改bash二进制文件遵循以下顺序> adb remount> adb push bash / system / bin /> adb shell> cd / system / bin> chm..._adb shell mv 权限

投影仪-相机标定_相机-投影仪标定-程序员宅基地

文章浏览阅读6.8k次,点赞12次,收藏125次。1. 单目相机标定引言相机标定已经研究多年,标定的算法可以分为基于摄影测量的标定和自标定。其中,应用最为广泛的还是张正友标定法。这是一种简单灵活、高鲁棒性、低成本的相机标定算法。仅需要一台相机和一块平面标定板构建相机标定系统,在标定过程中,相机拍摄多个角度下(至少两个角度,推荐10~20个角度)的标定板图像(相机和标定板都可以移动),即可对相机的内外参数进行标定。下面介绍张氏标定法(以下也这么称呼)的原理。原理相机模型和单应矩阵相机标定,就是对相机的内外参数进行计算的过程,从而得到物体到图像的投影_相机-投影仪标定

Wayland架构、渲染、硬件支持-程序员宅基地

文章浏览阅读2.2k次。文章目录Wayland 架构Wayland 渲染Wayland的 硬件支持简 述: 翻译一篇关于和 wayland 有关的技术文章, 其英文标题为Wayland Architecture .Wayland 架构若是想要更好的理解 Wayland 架构及其与 X (X11 or X Window System) 结构;一种很好的方法是将事件从输入设备就开始跟踪, 查看期间所有的屏幕上出现的变化。这就是我们现在对 X 的理解。 内核是从一个输入设备中获取一个事件,并通过 evdev 输入_wayland

推荐文章

热门文章

相关标签