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);
串口驱动实现操作流程
- 定义一个uart_driver的变量,并初始化;
- 使用uart_register_driver来注册这个驱动;
- 初始化uart_port和ops函数表;
- 调用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...