http://man7.org/linux/man-pages/man3/termios.3.html //終端設備各flag 含義, termbits.h
https://www.cmrr.umn.edu/~strupp/serial.html
http://www.voidcn.com/article/p-ridwjfzg-bba.html
查看實際串口對應ttyS 的哪個:
root@999:/# cat /proc/tty/driver/serial
serinfo:1.0 driver revision:
0: uart:16550A mmio:0x50024000 irq:5 tx:42893 rx:334 RTS|DTR|DSR|CD|RI
1: uart:16550A mmio:0x5002A000 irq:6 tx:16403 rx:10270 fe:1954 brk:4057 RTS|DTR|DSR|CD|RI
2: uart:16550A mmio:0x5002C000 irq:7 tx:0 rx:0 CTS|DSR|CD|RI
3: uart:16550A mmio:0x5002E000 irq:8 tx:0 rx:0 CTS|DSR|CD|RI
對於16進制數據55aaH,當採用8位數據位、1位停止位傳輸時,它在信號線上的波形如圖1(TTL電平)和圖2(RS-232電平)所示。 (先傳第一個字節55,再傳第二個字節aa,每個字節都是從低位向高位逐位傳輸)
圖1 TTL電平的串行數據幀格式(55aah)
圖2 RS-232電平的串行數據幀格式(55aah)
1. 轉載自 https://www.cnblogs.com/the-tops/p/5621207.html
stty 配置串口用法
在linux/unix平臺上的 sqlplus中,如果輸錯了字符,要想刪除,習慣性的按下backspace鍵後,發現非但沒有刪除想要刪掉的字符,還多出了兩個字符^H。當然,我們 可以同時按下ctrl+backspace鍵來刪除,但對於習慣了用backspace來刪除的用戶,這樣很不爽。這可以通過修改tty終端的設置來實現 backspace刪除功能。通過使用stty命令,就可以查看或者修改終端的按鍵設置。
例如,設置backspace爲刪除鍵:
[oracle10g@linux]$ stty erase ^h
如果要改回使用ctrl+backspace爲刪除鍵
[oracle10g@linux]$ stty erase ^?
[轉載注]在設置backspace時,最好先在shell提示符下按一下backspace鍵試一下,如果顯示^h就設置成stty erase ^h, 如果^?就用stty erease ^?
如果需要重啓後自動設置終端,可以將上述命令加入到profile中。
可以通過stty -a命令來查看所有的終端設置。下面是在linux下執行的輸出:
[oracle10g@linux]$ stty -a
speed 38400 baud; rows 66; columns 132; line = 0;
intr = ^C; quit = ^\; erase = ^H; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R;
werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
-parenb -parodd cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany -imaxbel
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke
其中:
- eof : 輸入結束
- erase : 向後刪除字符,
- intr : 中斷當前程序
- kill : 刪除整條命令
- quit :退出當前程序
- start : 啓動屏幕輸出
- stop :停止屏幕輸出;
- susp : terminal stop當前程序。
-------------------
下面是我的.bashrc下面的相關設置
# Terminal-related commands, tput, tset, stty, etc should not be
# executed by vuelogin or dtlogin. These login environments set VUE # andVUE # andDT accordingly.
#
if [ ! "VUE"−a!"VUE"−a!"DT" ]; then
# Terminal specific commands...
#tty -s && stty intr ^c susp ^z kill ^X erase ^h quit ^\\ eof ^d
tty -s && stty intr ^c susp ^z kill ^X erase ^? quit ^\\ eof ^d
符部分其他用法:
1. stty size 打印終端行數和列數
2,在命令行下禁止輸出小寫
stty olcuc #開啓
stty -olcuc#恢復
3,打印出終端的行數和列數
stty size
4,改變ctrl+D的方法:
stty eof "string"
系統默認是ctrl+D來表示文件的結束,而通過這種方法,可以改變!
5,屏蔽顯示
stty -echo #禁止回顯
stty echo #打開回顯
測試方法:
stty -echo;read;stty echo;read
6,忽略回車符
stty igncr #開啓
stty -igncr#恢復
7 .利用它設置我們的串口打印操作信息。
stty -F /dev/ttyS0 speed 115200 cs8 -parenb -cstopb -echo
解釋:通過stty設置/dev/ttyS0串口, 波特率爲115200 ,數據位cs8,奇偶校驗位-parenb,停止位-cstopb,同時-echo禁止終端回顯。
二:用法
1. stty size 打印終端行數和列數
2,在命令行下禁止輸出小寫
stty olcuc #開啓
stty -olcuc#恢復
3,打印出終端的行數和列數
stty size
4,改變ctrl+D的方法:
stty eof "string"
系統默認是ctrl+D來表示文件的結束,而通過這種方法,可以改變!
5,屏蔽顯示
stty -echo #禁止回顯
stty echo #打開回顯
測試方法:
stty -echo;read;stty echo;read
6,忽略回車符
stty igncr #開啓
stty -igncr#恢復
7 .利用它設置我們的串口打印操作信息。
stty -F /dev/ttyS0 speed 115200 cs8 -parenb -cstopb -echo
解釋:通過stty設置/dev/ttyS0串口, 波特率爲115200 ,數據位cs8,奇偶校驗位-parenb,停止位-cstopb,同時-echo禁止終端回顯。
2. 轉載 https://blog.csdn.net/skyflying2012/article/details/41078349
linux kernel下輸入輸出console如何實現
3. 轉載 https://blog.csdn.net/yyyks/article/details/7242267
8250 driver Linux多串口驅動解析 xr16v554
一:前言
前一段時間自己實踐了一下8250芯片串口驅動的編寫。今天就在此基礎上分析一下 linux kernel 自帶的串口驅動。畢竟只有對比專業的驅動代碼才能更好的進步, 同以往一樣,基於linux kernel2.6.25.
相應驅動代碼位於: linux-2.6.25/drivers/serial/8250.c。
二:8250串口驅動初始化
相應的初始化函數爲 serial8250_init().代碼如下 :
static int __init serial8250_init(void)
{
int ret, i;
if (nr_uarts > UART_NR)
nr_uarts = UART_NR;
printk(KERN_INFO "Serial: 8250/16550 driver "
"%d ports, IRQ sharing %sabled/n", nr_uarts,
share_irqs ? "en" : "dis");
for (i = 0; i < NR_IRQS; i++)
spin_lock_init(&irq_lists[i].lock);
ret = uart_register_driver(&serial8250_reg);
if (ret)
goto out;
serial8250_isa_devs = platform_device_alloc("serial8250",
PLAT8250_DEV_LEGACY);
if (!serial8250_isa_devs) {
ret = -ENOMEM;
goto unreg_uart_drv;
}
ret = platform_device_add(serial8250_isa_devs);
if (ret)
goto put_dev;
serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);
ret = platform_driver_register(&serial8250_isa_driver);
if (ret == 0)
goto out;
platform_device_del(serial8250_isa_devs);
put_dev:
platform_device_put(serial8250_isa_devs);
unreg_uart_drv:
uart_unregister_driver(&serial8250_reg);
out:
return ret;
}
這段代碼涉及到的知識要求,如platform ,uart等我們在之前都已經做過詳細的分析。這裏不再重複。在代碼中 UART_NR: 表示串口的個數。這個參數在編譯內核的時候可以自己配置,默認爲32 。
我們按照代碼中的流程一步一步進行研究。
1: 註冊uart_driver.
對應uart-driver的結構爲 serial8250_reg. 定義如下:
static struct uart_driver serial8250_reg = {
.owner = THIS_MODULE,
.driver_name = "serial",
.dev_name = "ttyS",
.major = TTY_MAJOR,
.minor = 64,
.nr = UART_NR,
.cons = SERIAL8250_CONSOLE,
};
TTY_MAJOR定義如下:
#define TTY_MAJOR 4
從上面可以看出。串口對應的設備節點爲/dev/ ttyS0 ~ /dev/ ttyS(UART_NR).設備節點號爲( 4,64 )起始的 UART_NR 個節點。
2:初始化並註冊 platform_device
相關代碼如下:
serial8250_isa_devs = platform_device_alloc("serial8250", PAT8250_DEV_LEGACY);
platform_device_add(serial8250_isa_devs);
可以看出。serial8250_isa_devs->name爲 serial8250。這個參數是在匹配 platform_device 和
platform_driver使用的。
3:爲uart-driver 添加 port.
相關代碼如下:
serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev)
跟進這個函數看一下:
static void __init
serial8250_register_ports(struct uart_driver *drv, struct device *dev)
{
int i;
serial8250_isa_init_ports();
for (i = 0; i < nr_uarts; i++) {
struct uart_8250_port *up = &serial8250_ports[i];
up->port.dev = dev;
uart_add_one_port(drv, &up->port);
}
}
在這裏函數裏,初始化了port.然後將它添加到 uart-driver 中。我們還注意到。生成的 deivce 節點,在 sysfs 中是位於 platform_deivce 對應目錄的下面 .
serial8250_isa_init_ports()代碼如下所示:
static void __init serial8250_isa_init_ports(void)
{
struct uart_8250_port *up;
static int first = 1;
int i;
if (!first)
return;
first = 0;
for (i = 0; i < nr_uarts; i++) {
struct uart_8250_port *up = &serial8250_ports[i];
up->port.line = i;
spin_lock_init(&up->port.lock);
init_timer(&up->timer);
up->timer.function = serial8250_timeout;
/*
* ALPHA_KLUDGE_MCR needs to be killed.
*/
up->mcr_mask = ~ALPHA_KLUDGE_MCR;
up->mcr_force = ALPHA_KLUDGE_MCR;
up->port.ops = &serial8250_pops;
}
for (i = 0, up = serial8250_ports;
i < ARRAY_SIZE(old_serial_port) && i < nr_uarts;i++, up++) {
up->port.iobase= old_serial_port[i].port;
up->port.irq= irq_canonicalize(old_serial_port[i].irq);
up->port.uartclk = old_serial_port[i].baud_base * 16;
up->port.flags = old_serial_port[i].flags;
up->port.hub6= old_serial_port[i].hub6;
up->port.membase= old_serial_port[i].iomem_base;
up->port.iotype= old_serial_port[i].io_type;
up->port.regshift = old_serial_port[i].iomem_reg_shift;
if (share_irqs)
up->port.flags |= UPF_SHARE_IRQ;
}
}
在這裏,我們關注一下注要成員的初始化。 Uart_port 的各項操作位於 serial8250_pops 中。 Iobase 、 irq 等成員是從 old_serial_por 這個結構中得來的,這個結構如下所示:
static const struct old_serial_port old_serial_port[] = {
SERIAL_PORT_DFNS /* defined in asm/serial.h */
}
#define SERIAL_PORT_DFNS /
/* UART CLK PORT IRQ FLAGS */ /
{ 0, BASE_BAUD, 0x3F8, 4, STD_COM_FLAGS }, /* ttyS0 */ /
{ 0, BASE_BAUD, 0x2F8, 3, STD_COM_FLAGS }, /* ttyS1 */ /
{ 0, BASE_BAUD, 0x3E8, 4, STD_COM_FLAGS }, /* ttyS2 */ /
{ 0, BASE_BAUD, 0x2E8, 3, STD_COM4_FLAGS }, /* ttyS3 */
從上面看到。前兩項對應了com1、 com2的各項參數。如寄存器首始地址, Irq 號等。後面兩項不太清楚。在上面的代碼中,我們看到了 uart_port 各項成員的初始化。在後面很多操作中需要用到這個成員。我們等分析相關部份的時候,再到這個地方來看相關成員的值。
4:註冊platform_driver
相關代碼如下:
platform_driver_register(&serial8250_isa_driver);
serial8250_isa_driver定義如下:
static struct platform_driver serial8250_isa_driver = {
.probe= serial8250_probe,
.remove= __devexit_p(serial8250_remove),
.suspend= serial8250_suspend,
.resume= serial8250_resume,
.driver = {
.name = "serial8250",
.owner = THIS_MODULE,
},
}
爲了以後把分析集中到具體的驅動部份.我們先把這個 platform_driver 引出的問題講述完 .
經過前面有關platform的分析我們知道,這個 platform 的 name 爲 ” serial8250” 。剛好跟前面註冊的 platform_device 相匹配 . 會調用 platform_driver-> probe. 在這裏 , 對應的接口爲 :
serial8250_probe().代碼如下:
static int __devinit serial8250_probe(struct platform_device *dev)
{
struct plat_serial8250_port *p = dev->dev.platform_data;
struct uart_port port;
int ret, i;
memset(&port, 0, sizeof(struct uart_port));
for (i = 0; p && p->flags != 0; p++, i++) {
port.iobase= p->iobase;
port.membase= p->membase;
port.irq= p->irq;
port.uartclk= p->uartclk;
port.regshift= p->regshift;
port.iotype= p->iotype;
port.flags= p->flags;
port.mapbase= p->mapbase;
port.hub6= p->hub6;
port.private_data= p->private_data;
port.dev= &dev->dev;
if (share_irqs)
port.flags |= UPF_SHARE_IRQ;
ret = serial8250_register_port(&port);
if (ret < 0) {
dev_err(&dev->dev, "unable to register port at index %d "
"(IO%lx MEM%llx IRQ%d): %d/n", i,
p->iobase, (unsigned long long)p->mapbase,
p->irq, ret);
}
}
return 0;
}
從上述代碼可以看出.會 將 dev->dev.platform_data 所代表的 port 添加到 uart_driver 中。這個
dev->dev.platform_data 究竟代表什麼. 我們在看到的時候再來研究它.
現在,我們把精力集中到 uart_port 的操作上。
三:config_port 過程
在初始化uart_port的過程中 , 在以下代碼片段 :
serial8250_isa_init_ports(void)
{ ......
for (i = 0, up = serial8250_ports;
i < ARRAY_SIZE(old_serial_port) && i < nr_uarts;
i++, up++) {
up->port.iobase = old_serial_port[i].port;
up->port.irq = irq_canonicalize(old_serial_port[i].irq);
up->port.uartclk = old_serial_port[i].baud_base * 16;
up->port.flags = old_serial_port[i].flags;
up->port.hub6 = old_serial_port[i].hub6;
up->port.membase = old_serial_port[i].iomem_base;
up->port.iotype = old_serial_port[i].io_type;
up->port.regshift = old_serial_port[i].iomem_reg_shift;
if (share_irqs)
up->port.flags |= UPF_SHARE_IRQ;
}
}
而old_serial_port又定義如下 :
static const struct old_serial_port old_serial_port[] = {
SERIAL_PORT_DFNS /* defined in asm/serial.h */
};
#define SERIAL_PORT_DFNS /
/* UART CLK PORT IRQ FLAGS */ /
{ 0, BASE_BAUD, 0x3F8, 4, STD_COM_FLAGS }, /* ttyS0 */ /
{ 0, BASE_BAUD, 0x2F8, 3, STD_COM_FLAGS }, /* ttyS1 */ /
{ 0, BASE_BAUD, 0x3E8, 4, STD_COM_FLAGS }, /* ttyS2 */ /
{ 0, BASE_BAUD, 0x2E8, 3, STD_COM4_FLAGS }, /* ttyS3 */
由此可見,port->flags被定義成了 STD_COM_FLAGS, 定義如下:
#ifdef CONFIG_SERIAL_DETECT_IRQ
#define STD_COM_FLAGS (ASYNC_BOOT_AUTOCONF | ASYNC_SKIP_TEST |
ASYNC_AUTO_IRQ)
#define STD_COM4_FLAGS (ASYNC_BOOT_AUTOCONF | ASYNC_AUTO_IRQ)
#else
#define STD_COM_FLAGS (ASYNC_BOOT_AUTOCONF | ASY NC_SKIP_TEST)
#define STD_COM4_FLAGS ASYNC_BOOT_AUTOCONF
#endif
從這裏看到,不管是否自己探測 IRQ, 都會定義 ASYNC_BOOT_AUTOCONF 。這樣 , 在
uart_add_one_port()的時候。就會進入到port->config_port 來配置端口,在 8250 中 , 對應的接口爲 :
serial8250_config_port(). 代碼如下:
static void serial8250_config_port(struct uart_port *port, int flags)
{
struct uart_8250_port *up = (struct uart_8250_port *)port;
int probeflags = PROBE_ANY;
int ret;
/*
* Find the region that we can probe for. This in turn
* tells us whether we can probe for the type of port.
*/
ret = serial8250_request_std_resource(up);
if (ret < 0)
return;
ret = serial8250_request_rsa_resource(up);
if (ret < 0)
probeflags &= ~PROBE_RSA;
if (flags & UART_CONFIG_TYPE)
autoconfig(up, probeflags);
if (up->port.type != PORT_UNKNOWN && flags & UART_CONFIG_IRQ)
autoconfig_irq(up);
if (up->port.type != PORT_RSA && probeflags & PROBE_RSA)
serial8250_release_rsa_resource(up);
if (up->port.type == PORT_UNKNOWN)
serial8250_release_std_resource(up);
}
serial8250_request_std_resource 和serial8250_request_rsa_resource 都是分配操作的端口,回
顧在前面的分析中,port的相關參數會從 old_serial_port 中取得 . 而 old_serial_port 中又沒有定義 port->iotype 和 port-> regshift. 也就是說對應這兩項全爲 0. 而
#define UPIO_PORT (0)
即表示是要操作I/O端口。自己閱讀這兩個函數,會發現在 serial8250_request_rsa_resource() 中是會返回失敗的。另外, 在uart_add_one_port() 在進行端口匹配時 , 會先置 flags 爲 UART_CONFIG_TYPE 。這樣 , 在本次操作中 , if (flags & UART_CONFIG_TYPE)是會滿足的,相應的就會進入 autoconfig() 。
代碼如下,這段代碼比較長 , 分段分析如下 :
static void autoconfig(struct uart_8250_port *up, unsigned int probeflags)
{
unsigned char status1, scratch, scratch2, scratch3;
unsigned char save_lcr, save_mcr;
unsigned long flags;
if (!up->port.iobase && !up->port.mapbase && !up->port.membase)
return;
DEBUG_AUTOCONF("ttyS%d: autoconf (0x%04x, 0x%p): ",
up->port.line, up->port.iobase, up->port.membase);
/*
* We really do need global IRQs disabled here - we're going to
* be frobbing the chips IRQ enable register to see if it exists.
*/
spin_lock_irqsave(&up->port.lock, flags);
up->capabilities = 0;
up->bugs = 0;
if (!(up->port.flags & UPF_BUGGY_UART)) {
/*
* Do a simple existence test first; if we fail this,
* there's no point trying anything else.
*
* 0x80 is used as a nonsense port to prevent against
* false positives due to ISA bus float. The
* assumption is that 0x80 is a non-existent port;
* which should be safe since include/asm/io.h also
* makes this assumption.
*
* Note: this is safe as long as MCR bit 4 is clear
* and the device is in "PC" mode.
*/
scratch = serial_inp(up, UART_IER);
serial_outp(up, UART_IER, 0);
#ifdef __i386__
outb(0xff, 0x080);
#endif
/*
* Mask out IER[7:4] bits for test as some UARTs (e.g. TL
* 16C754B) allow only to modify them if an EFR bit is set.
*/
scratch2 = serial_inp(up, UART_IER) & 0x0f;
serial_outp(up, UART_IER, 0x0F);
#ifdef __i386__
outb(0, 0x080);
#endif
scratch3 = serial_inp(up, UART_IER) & 0x0f;
serial_outp(up, UART_IER, scratch);
if (scratch2 != 0 || scratch3 != 0x0F) {
/* * We failed; there's nothing here
*/
DEBUG_AUTOCONF("IER test failed (%02x, %02x) ",
scratch2, scratch3);
goto out;
}
}
在這裏,先對 8250 是否存在做一個簡單的判斷 ,先將IER中的值取得 , 這樣可以在測試之後恢復 IER 中的值 ,然後往IER中寫放 0 , 再將IER 中的值取出 , 又往IER 中寫入 0xOF. 然後再將 IER 中的值取出 . 最後將 IER 中的值恢復到原值 。 這樣就可以根據寫入的值和讀出的值是否相等來判斷該寄存器是否存在.
save_mcr = serial_in(up, UART_MCR);
save_lcr = serial_in(up, UART_LCR);
在這裏,先將 MCR 和 LCR 中的值取出 ,因爲在後面的操作中會使用這兩個寄存器 , 方便使用完了恢復
/*
* Check to see if a UART is really there. Certain broken
* internal modems based on the Rockwell chipset fail this
* test, because they apparently don't implement the loopback
* test mode. So this test is skipped on the COM 1 through
* COM 4 ports. This *should* be safe, since no board
* manufacturer would be stupid enough to design a board
* that conflicts with COM 1-4 --- we hope!
*/
if (!(up->port.flags & UPF_SKIP_TEST)) {
serial_outp(up, UART_MCR, UART_MCR_LOOP | 0x0A);
status1 = serial_inp(up, UART_MSR) & 0xF0;
serial_outp(up, UART_MCR, save_mcr);
if (status1 != 0x90) {
DEBUG_AUTOCONF("LOOP test failed (%02x) ",
status1);
goto out;
}
}
在這裏,將 MCR的自檢位置位 , 並允許向中斷控制器產生中斷 ,而且產生RTS信號 ,這樣MSR寄存器應該可以檢測到這個信號 . 如果沒有檢測到 . 自測失 敗 !MCR 寄存器已經操作完了 , 恢復 MCR 寄存器的原值 。
/*
* We're pretty sure there's a port here. Lets find out what
* type of port it is. The IIR top two bits allows us to find
* out if it's 8250 or 16450, 16550, 16550A or later. This
* determines what we test for next.
*
* We also initialise the EFR (if any) to zero for later. The
* EFR occupies the same register location as the FCR and IIR. */
serial_outp(up, UART_LCR, 0xBF);
serial_outp(up, UART_EFR, 0);
serial_outp(up, UART_LCR, 0);
serial_outp(up, UART_FCR, UART_FCR_ENABLE_FIFO);
scratch = serial_in(up, UART_IIR) >> 6;
DEBUG_AUTOCONF("iir=%d ", scratch);
switch (scratch) {
case 0:
autoconfig_8250(up);
break;
case 1:
up->port.type = PORT_UNKNOWN;
break;
case 2:
up->port.type = PORT_16550;
break;
case 3:
autoconfig_16550a(up);
break;
}
在這裏,先允許使用 FIFO 寄存器 , 然後通過 IIR 寄存的高二位來判斷芯片的類型
#ifdef CONFIG_SERIAL_8250_RSA
/*
* Only probe for RSA ports if we got the region.
*/
if (up->port.type == PORT_16550A && probeflags & PROBE_RSA) {
int i;
for (i = 0 ; i < probe_rsa_count; ++i) {
if (probe_rsa[i] == up->port.iobase &&
__enable_rsa(up)) {
up->port.type = PORT_RSA;
break;
}
}
}
#endif
#ifdef CONFIG_SERIAL_8250_AU1X00 /* if access method is AU, it is a 16550 with a quirk */
if (up->port.type == PORT_16550A && up->port.iotype == UPIO_AU)
up->bugs |= UART_BUG_NOMSR;
#endif
serial_outp(up, UART_LCR, save_lcr);
if (up->capabilities != uart_config[up->port.type].flags) {
printk(KERN_WARNING
"ttyS%d: detected caps %08x should be %08x/n",
up->port.line, up->capabilities,
uart_config[up->port.type].flags);
}
up->port.fifosize = uart_config[up->port.type].fifo_size;
up->capabilities = uart_config[up->port.type].flags;
up->tx_loadsz = uart_config[up->port.type].tx_loadsz;
if (up->port.type == PORT_UNKNOWN)
goto out;
/*
* Reset the UART.
*/
#ifdef CONFIG_SERIAL_8250_RSA
if (up->port.type == PORT_RSA)
serial_outp(up, UART_RSA_FRR, 0);
#endif
serial_outp(up, UART_MCR, save_mcr);
serial8250_clear_fifos(up);
serial_in(up, UART_RX);
if (up->capabilities & UART_CAP_UUE)
serial_outp(up, UART_IER, UART_IER_UUE);
else
serial_outp(up, UART_IER, 0);
out:
spin_unlock_irqrestore(&up->port.lock, flags);
DEBUG_AUTOCONF("type=%s/n", uart_config[up->port.type].name);
}
最後,復位串口控制器,我們假設使用的是 8250 串口芯片 ,在芯片類型判斷的時候就會進入autoconfig_8250().代碼如 下 :
static void autoconfig_8250(struct uart_8250_port *up) {
unsigned char scratch, status1, status2;
up->port.type = PORT_8250;
scratch = serial_in(up, UART_SCR);
serial_outp(up, UART_SCR, 0xa5);
status1 = serial_in(up, UART_SCR);
serial_outp(up, UART_SCR, 0x5a);
status2 = serial_in(up, UART_SCR);
serial_outp(up, UART_SCR, scratch);
if (status1 == 0xa5 && status2 == 0x5a)
up->port.type = PORT_16450;
}
如果存在SCR寄存器 , 則芯片是 16450 類型的 ,這不是我們需要研究的芯片 。
回到serial8250_config_port()中 ,代碼片 段如下所示 :
static void serial8250_config_port(struct uart_port *port, int flags)
{
……
if (flags & UART_CONFIG_TYPE)
autoconfig(up, probeflags);
if (up->port.type != PORT_UNKNOWN && flags & UART_CONFIG_IRQ)
autoconfig_irq(up);
if (up->port.type != PORT_RSA && probeflags & PROBE_RSA)
serial8250_release_rsa_resource(up);
if (up->port.type == PORT_UNKNOWN)
serial8250_release_std_resource(up);
}
如果定義了自己控測IRQ號 (CONFIG_SERIAL_8250_DETECT_IRQ), 一般情況下, 編譯內核的時候一般都將其賦值爲 CONFIG_SERIAL_8250_DETECT_IRQ = y, 此時就會進入autoconfig_irq(). 代碼如下 :
static void autoconfig_irq(struct uart_8250_port *up)
{
unsigned char save_mcr, save_ier;
unsigned char save_ICP = 0;
unsigned int ICP = 0;
unsigned long irqs;
int irq;
if (up->port.flags & UPF_FOURPORT) {
ICP = (up->port.iobase & 0xfe0) | 0x1f;
save_ICP = inb_p(ICP);
outb_p(0x80, ICP);
(void) inb_p(ICP);
}
/* forget possible initially masked and pending IRQ */
probe_irq_off(probe_irq_on());
save_mcr = serial_inp(up, UART_MCR);
save_ier = serial_inp(up, UART_IER);
serial_outp(up, UART_MCR, UART_MCR_OUT1 | UART_MCR_OUT2);
irqs = probe_irq_on();
serial_outp(up, UART_MCR, 0);
udelay(10);
if (up->port.flags & UPF_FOURPORT) {
serial_outp(up, UART_MCR,
UART_MCR_DTR | UART_MCR_RTS);
} else {
serial_outp(up, UART_MCR,
UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2);
}
serial_outp(up, UART_IER, 0x0f); /* enable all intrs */
(void)serial_inp(up, UART_LSR);
(void)serial_inp(up, UART_RX);
(void)serial_inp(up, UART_IIR);
(void)serial_inp(up, UART_MSR);
serial_outp(up, UART_TX, 0xFF);
udelay(20);
irq = probe_irq_off(irqs);
serial_outp(up, UART_MCR, save_mcr);
serial_outp(up, UART_IER, save_ier);
if (up->port.flags & UPF_FOURPORT)
outb_p(save_ICP, ICP);
up->port.irq = (irq > 0) ? irq : 0;
}
在上述代碼的操作中,先將 8250 相關中斷允許寄存器全打開 ,然後調用驅動使用的函數, 當它不得不探測來決定哪個中斷線被設備在使用 ,probe_irq_on()將中斷暫時關掉, 然後配置MCR 寄存器使之發送 DTR 和 RTS. 之後再用 probe_irq_off() 來檢測 IRQ 號 , 如果檢測成功, 則值賦值給 port->irq ,進行到這裏,conifg_port動作就完成了 。經過這個config_port過程後 , 我們發現 , 並沒有對 serial8250_isa_devs->dev-> platform_data 賦值, 也就是說platform_driver->probe 函數並無實質性的處理 , 在第一次for 循環的時 , 就會因條件不符而退出 .
四: startup操作
在前面分析uart驅動架構的時候 , 曾說過 , 在 open 的時候 , 會調用 port->startup() , 在本次分析的驅動中 , 對應接口爲serial8250_startup() 分段分析如下 :
static int serial8250_startup(struct uart_port *port)
{
struct uart_8250_port *up = (struct uart_8250_port *)port;
unsigned long flags;
unsigned char lsr, iir;
int retval;
up->capabilities = uart_config[up->port.type].flags;
up->mcr = 0;
if (up->port.type == PORT_16C950) {
/* Wake up and initialize UART */
up->acr = 0;
serial_outp(up, UART_LCR, 0xBF);
serial_outp(up, UART_EFR, UART_EFR_ECB);
serial_outp(up, UART_IER, 0);
serial_outp(up, UART_LCR, 0);
serial_icr_write(up, UART_CSR, 0); /* Reset the UART */
serial_outp(up, UART_LCR, 0xBF);
serial_outp(up, UART_EFR, UART_EFR_ECB);
serial_outp(up, UART_LCR, 0);
}
#ifdef CONFIG_SERIAL_8250_RSA
/*
* If this is an RSA port, see if we can kick it up to the
* higher speed clock.
*/
enable_rsa(up);
#endif
/*
* Clear the FIFO buffers and disable them.
* (they will be reenabled in set_termios())
*/
serial8250_clear_fifos(up);
上面的代碼都不是對應8250芯片的情況
/*
* Clear the interrupt registers. */
(void) serial_inp(up, UART_LSR);
(void) serial_inp(up, UART_RX);
(void) serial_inp(up, UART_IIR);
(void) serial_inp(up, UART_MSR);
復位LSR,RX,IIR,MSR寄存器
/*
* At this point, there's no way the LSR could still be 0xff;
* if it is, then bail out, because there's likely no UART
* here.
*/
if (!(up->port.flags & UPF_BUGGY_UART) &&
(serial_inp(up, UART_LSR) == 0xff)) {
printk("ttyS%d: LSR safety check engaged!/n", up->port.line);
return -ENODEV;
}
若LSR寄存器中的值爲 0xFF. 異常
/*
* For a XR16C850, we need to set the trigger levels
*/
if (up->port.type == PORT_16850) {
unsigned char fctr;
serial_outp(up, UART_LCR, 0xbf);
fctr = serial_inp(up, UART_FCTR) & ~(UART_FCTR_RX|UART_FCTR_TX);
serial_outp(up, UART_FCTR, fctr | UART_FCTR_TRGD | UART_FCTR_RX);
serial_outp(up, UART_TRG, UART_TRG_96);
serial_outp(up, UART_FCTR, fctr | UART_FCTR_TRGD | UART_FCTR_TX);
serial_outp(up, UART_TRG, UART_TRG_96);
serial_outp(up, UART_LCR, 0);
}
16850系列芯片的處理, 忽略
if (is_real_interrupt(up->port.irq)) { /*
* Test for UARTs that do not reassert THRE when the
* transmitter is idle and the interrupt has already
* been cleared. Real 16550s should always reassert
* this interrupt whenever the transmitter is idle and
* the interrupt is enabled. Delays are necessary to
* allow register changes to become visible. */
spin_lock_irqsave(&up->port.lock, flags);
wait_for_xmitr(up, UART_LSR_THRE);
serial_out_sync(up, UART_IER, UART_IER_THRI);
udelay(1); /* allow THRE to set */
serial_in(up, UART_IIR);
serial_out(up, UART_IER, 0);
serial_out_sync(up, UART_IER, UART_IER_THRI);
udelay(1); /* allow a working UART time to re-assert THRE */
iir = serial_in(up, UART_IIR);
serial_out(up, UART_IER, 0);
spin_unlock_irqrestore(&up->port.lock, flags);
/*
* If the interrupt is not reasserted, setup a timer to
* kick the UART on a regular basis.
*/
if (iir & UART_IIR_NO_INT) {
pr_debug("ttyS%d - using backup timer/n", port->line);
up->timer.function = serial8250_backup_timeout;
up->timer.data = (unsigned long)up;
mod_timer(&up->timer, jiffies +
poll_timeout(up->port.timeout) + HZ / 5);
}
}
如果中斷號有效,還要進一步判斷這個中斷號是否有效 . 具體操作爲 , 先等待 8250 發送寄存器空 . 然後允許發送中斷空的中斷 . 然後判斷 IIR 寄存器是 否收到中斷 . 如果有沒有收到中斷 , 則說明這根中斷線無效 。只能採用輪詢的方式.關於輪詢方式 , 我們在之後再以獨立章節的形式給出分析
/*
* If the "interrupt" for this port doesn't correspond with any
* hardware interrupt, we use a timer-based system. The original
* driver used to do this with IRQ0.
*/
if (!is_real_interrupt(up->port.irq)) {
up->timer.data = (unsigned long)up;
mod_timer(&up->timer, jiffies + poll_timeout(up->port.timeout));
} else {
retval = serial_link_irq_chain(up);
if (retval)
return retval;
} 如果沒有設置中斷號, 則採用輪詢方式. 如果中斷後有效. 流程轉入serial_link_irq_chain(). 在這個裏面 .
會註冊中斷處理函 數.
/*
* Now, initialize the UART
*/
serial_outp(up, UART_LCR, UART_LCR_WLEN8);
spin_lock_irqsave(&up->port.lock, flags);
if (up->port.flags & UPF_FOURPORT) {
if (!is_real_interrupt(up->port.irq))
up->port.mctrl |= TIOCM_OUT1;
} else
/*
* Most PC uarts need OUT2 raised to enable interrupts.
*/
if (is_real_interrupt(up->port.irq))
up->port.mctrl |= TIOCM_OUT2;
serial8250_set_mctrl(&up->port, up->port.mctrl);
/*
* Do a quick test to see if we receive an
* interrupt when we enable the TX irq.
*/
serial_outp(up, UART_IER, UART_IER_THRI);
lsr = serial_in(up, UART_LSR);
iir = serial_in(up, UART_IIR);
serial_outp(up, UART_IER, 0);
if (lsr & UART_LSR_TEMT && iir & UART_IIR_NO_INT) {
if (!(up->bugs & UART_BUG_TXEN)) {
up->bugs |= UART_BUG_TXEN;
pr_debug("ttyS%d - enabling bad tx status workarounds/n",
port->line);
}
} else {
up->bugs &= ~UART_BUG_TXEN;
}
spin_unlock_irqrestore(&up->port.lock, flags);
/*
* Clear the interrupt registers again for luck, and clear the
* saved flags to avoid getting false values from polling * routines or the previous session.
*/
serial_inp(up, UART_LSR);
serial_inp(up, UART_RX);
serial_inp(up, UART_IIR);
serial_inp(up, UART_MSR);
up->lsr_saved_flags = 0;
up->msr_saved_flags = 0;
/*
* Finally, enable interrupts. Note: Modem status interrupts
* are set via set_termios(), which will be occurring imminently
* anyway, so we don't enable them here.
*/
up->ier = UART_IER_RLSI | UART_IER_RDI;
serial_outp(up, UART_IER, up->ier);
if (up->port.flags & UPF_FOURPORT) {
&nbs p; unsigned int icp;
/*
* Enable interrupts on the AST Fourport board
*/
icp = (up->port.iobase & 0xfe0) | 0x01f;
outb_p(0x80, icp);
(void) inb_p(icp);
}
return 0;
}
最後,就是對 8250 芯片的初始化了 ,包括:在 LCR中設置數據格式 , 在 MCR 中設置允許中斷到 8259. 在 IER 中設置相關允許位 。另外在open的時候 , 還會調用 port-> enable_ms () 接口 , 在本例中對應爲 :
serial8250_enable_ms(). 代碼如下:
static void serial8250_enable_ms(struct uart_port *port)
{
struct uart_8250_port *up = (struct uart_8250_port *)port;
/* no MSR capabilities */
if (up->bugs & UART_BUG_NOMSR)
return;
up->ier |= UART_IER_MSI;
serial_out(up, UART_IER, up->ier);
}
即允許moden中斷
五:數據發送的操作
在uart驅動架構中分析過 , 在發送數據的時候 ,uart 層先會將數據放入 circ_buffer 。 最後再調用port->start_tx().
在這裏,這個接口對應爲 serial8250_start_tx(). 代碼如下 :
static void serial8250_start_tx(struct uart_port *port)
{
struct uart_8250_port *up = (struct uart_8250_port *)port;
if (!(up->ier & UART_IER_THRI)) {
up->ier |= UART_IER_THRI;
serial_out(up, UART_IER, up->ier);
if (up->bugs & UART_BUG_TXEN) {
unsigned char lsr, iir;
lsr = serial_in(up, UART_LSR);
up->lsr_saved_flags |= lsr & LSR_SAVE_FLAGS;
iir = serial_in(up, UART_IIR) & 0x0f;
if ((up->port.type == PORT_RM9000) ?
(lsr & UART_LSR_THRE &&
(iir == UART_IIR_NO_INT || iir == UART_IIR_THRI)) :
(lsr & UART_LSR_TEMT && iir & UART_IIR_NO_INT))
transmit_chars(up);
}
}
/*
* Re-enable the transmitter if we disabled it.
*/
if (up->port.type == PORT_16C950 && up->acr & UART_ACR_TXDIS) {
up->acr &= ~UART_ACR_TXDIS;
serial_icr_write(up, UART_ACR, up->acr);
}
}
這個函數非常 簡單.如果沒有定義發送空中斷 . 則在 IER 中打開這個中斷 ,關於TXEN上的 bug 修復和 16C950 類型的芯片不是我們所關注的部份 . 。 那, 這裏只是打開了這個中斷 , 寫數據到芯片的這個過程是在什麼地方完成的呢? 是在中斷處理中 , 如果是發送空的中斷, 就將 circ buffer 中的數據寫出發送寄存器 , 跟蹤一下代碼 , 中斷處理函數爲serial8250_interrupt().
static irqreturn_t serial8250_interrupt(int irq, void *dev_id)
{
struct irq_info *i = dev_id;
struct list_head *l, *end = NULL;
int pass_counter = 0, handled = 0;
DEBUG_INTR("serial8250_interrupt(%d)...", irq);
spin_lock(&i->lock);
l = i->head;
do {
struct uart_8250_port *up;
unsigned int iir;
up = list_entry(l, struct uart_8250_port, list);
iir = serial_in(up, UART_IIR);
if (!(iir & UART_IIR_NO_INT)) {
serial8250_handle_port(up);
handled = 1;
end = NULL;
} else if (up->port.iotype == UPIO_DWAPB &&
(iir & UART_IIR_BUSY) == UART_IIR_BUSY) {
/* The DesignWare APB UART has an Busy Detect (0x07)
* interrupt meaning an LCR write attempt occured while the
* UART was busy. The interrupt must be cleared by reading
* the UART status register (USR) and the LCR re-written. */
unsigned int status;
status = *(volatile u32 *)up->port.private_data;
serial_out(up, UART_LCR, up->lcr);
handled = 1;
end = NULL;
} else if (end == NULL)
end = l;
l = l->next;
if (l == i->head && pass_counter++ > PASS_LIMIT) {
/* If we hit this, we're dead. */
printk(KERN_ERR "serial8250: too much work for "
"irq%d/n", irq);
break;
}
} while (l != end);
spin_unlock(&i->lock);
DEBUG_INTR("end./n");
return IRQ_RETVAL(handled);
}
這裏可能有個疑問的地方,掛在這個鏈表上的到底是什麼 ,這我們要從serial_link_irq_chain()來說起 。該函數代碼如下:
static int serial_link_irq_chain(struct uart_8250_port *up)
{
struct irq_info *i = irq_lists + up->port.irq;
int ret, irq_flags = up->port.flags & UPF_SHARE_IRQ ? IRQF_SHARED : 0;
spin_lock_irq(&i->lock);
if (i->head) {
list_add(&up->list, i->head);
spin_unlock_irq(&i->lock);
ret = 0;
} else {
INIT_LIST_HEAD(&up->list);
i->head = &up->list;
spin_unlock_irq(&i->lock);
ret = request_irq(up->port.irq, serial8250_interrupt,
irq_flags, "serial", i);
if (ret < 0)
serial_do_unlink(i, up);
}
return ret;
}
從這裏看到,註冊中斷處理函數的參數 i 就是對應 irq_lists + up->port.irq. 即對應在 irq_lists 數組中的 port->irq 項 . 隨後 , 將註冊的 uart_8250_port 添加到了這個鏈表 。奇怪了,爲什麼要這麼做了 ? 我們返回 old_serial_port 的定義 看看 :
static const struct old_serial_port old_serial_port[] = {
SERIAL_PORT_DFNS /* defined in asm/serial.h */
};
#define SERIAL_PORT_DFNS /
/* UART CLK PORT IRQ FLAGS */ /
{ 0, BASE_BAUD, 0x3F8, 4, STD_COM_FLAGS }, /* ttyS0 */ /
{ 0, BASE_BAUD, 0x2F8, 3, STD_COM_FLAGS }, /* ttyS1 */ /
{ 0, BASE_BAUD, 0x3E8, 4, STD_COM_FLAGS }, /* ttyS2 */ /
{ 0, BASE_BAUD, 0x2E8, 3, STD_COM4_FLAGS }, /* ttyS3 */
在這裏,注意到同一個 IRQ 號會對應兩個 port , IRQ中發生中斷的時候 , 怎麼去判斷是哪一個 port 所引起的 ,當然方法有多種多樣 。 在這裏,8250驅動的作者是將不同的 port 鏈入到 IRQ 對應的鏈表來完成的 ,這樣,如果 IRQ 產生了中斷了 , 就判斷掛在該鏈表中的 port, 看中斷是否由它產生 。
經過這個分析之 後,我們應該很清楚 serial8250_interrupt() 中的處理流程了 。 對應產生IRQ 的 port, 流程會轉入 serial8250_handle_port()中 .代碼如下 :
static inline void
serial8250_handle_port(struct uart_8250_port *up)
{
unsigned int status;
unsigned long flags;
spin_lock_irqsave(&up->port.lock, flags);
status = serial_inp(up, UART_LSR);
DEBUG_INTR("status = %x...", status);
if (status & UART_LSR_DR)
receive_chars(up, &status);
check_modem_status(up);
if (status & UART_LSR_THRE)
transmit_chars(up);
spin_unlock_irqrestore(&up->port.lock, flags);
}
對於產生中斷的情況下,判斷髮送緩存區是否爲空 , 如果爲空 , 就可以發送數據了 ,對應的處理在transmit_chars(up), 如果接收緩存區滿, 就那接收 數據 , 這是在 receive_chars() 中處理的 , 對於接收數據, 我們在下一節再分析 。
transmit_chars()代碼如 下:
static void transmit_chars(struct uart_8250_port *up)
{
struct circ_buf *xmit = &up->port.info->xmit;
int count;
if (up->port.x_char) {
serial_outp(up, UART_TX, up->port.x_char);
up->port.icount.tx++;
up->port.x_char = 0;
return;
}
if (uart_tx_stopped(&up->port)) {
serial8250_stop_tx(&up->port);
return;
}
if (uart_circ_empty(xmit)) {
__stop_tx(up);
return;
}
count = up->tx_loadsz;
do {
serial_out(up, UART_TX, xmit->buf[xmit->tail]);
xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
up->port.icount.tx++;
if (uart_circ_empty(xmit))
break;
} while (--count > 0);
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
uart_write_wakeup(&up->port);
DEBUG_INTR("THRE...");
if (uart_circ_empty(xmit))
__stop_tx(up);
}
從上面的代碼看出, 會從xmit 中取出數據 , 然後將其寫入到發送寄存器中 , 特別的, 在 8250 芯片的情況下 , up->tx_loadsz 等於 1. 也就是說 , 一次只能傳送 1 個字節 。 如果緩存區的數據傳輸 完 了之後, 就會調用 __stop_tx() , 代碼如下:
static inline void __stop_tx(struct uart_8250_port *p)
{
if (p->ier & UART_IER_THRI) {
p->ier &= ~UART_IER_THRI;
serial_out(p, UART_IER, p->ier);
}
}
對應的,在 IER 中 ,將發送緩存區空的中斷關掉 .
六:數據 讀取操作
在前面的tty 驅動架構分析中 , 曾說過 , 在 tty_driver 中並末提供 read 接口 ,上層的read操作是直接到 ldsic 的緩存區中讀數據的 ,那ldsic的數據是怎麼送入進去的呢 ? 繼續看中斷處理中的數據接收流程 . 即爲 :
receive_chars().代碼片 段如下:
static void
receive_chars(struct uart_8250_port *up, unsigned int *status) {
……
……
uart_insert_char(&up->port, lsr, UART_LSR_OE, ch, flag);
}
最後流據會轉入uart_inset_char(), 這個函數是uart 層提供的一個接口 , 代碼如下 :
static inline void
uart_insert_char(struct uart_port *port, unsigned int status,
unsigned int overrun, unsigned int ch, unsigned int flag)
{
struct tty_struct *tty = port->info->tty;
if ((status & port->ignore_status_mask & ~overrun) == 0)
tty_insert_flip_char(tty, ch, flag);
/*
* Overrun is special. Since it's reported immediately,
* it doesn't affect the current character.
*/
if (status & ~port->ignore_status_mask & overrun)
tty_insert_flip_char(tty, 0, TTY_OVERRUN);
}
Tty_insert_filp()函數的代碼我們在之前已經分析過, 這裏不再贅述, 就這樣, 數據就直接交給了 ldisc.
七:輪詢操作
在前面已經分析到,如果沒有定義 irq 或者沒有控測到 irq 號 , 就會採用輪詢 。在代碼 中 採用定時器的方式去判斷是否有數據到來,或者將數據寫入 8250. 定時器對應的運行函數爲 serial8250_backup_timeout() ,代碼如下:
static void serial8250_backup_timeout(unsigned long data)
{
struct uart_8250_port *up = (struct uart_8250_port *)data;
unsigned int iir, ier = 0, lsr;
unsigned long flags;
/*
* Must disable interrupts or else we risk racing with the interrupt
* based handler.
*/
if (is_real_interrupt(up->port.irq)) {
ier = serial_in(up, UART_IER);
serial_out(up, UART_IER, 0);
}
iir = serial_in(up, UART_IIR);
/*
* This should be a safe test for anyone who doesn't trust the
* IIR bits on their UART, but it's specifically designed for
* the "Diva" UART used on the management processor on many HP
* ia64 and parisc boxes.
*/
spin_lock_irqsave(&up->port.lock, flags);
lsr = serial_in(up, UART_LSR);
up->lsr_saved_flags |= lsr & LSR_SAVE_FLAGS;
spin_unlock_irqrestore(&up->port.lock, flags);
if ((iir & UART_IIR_NO_INT) && (up->ier & UART_IER_THRI) &&
(!uart_circ_empty(&up->port.info->xmit) || up->port.x_char) &&
(lsr & UART_LSR_THRE)) {
iir &= ~(UART_IIR_ID | UART_IIR_NO_INT);
iir |= UART_IIR_THRI;
}
if (!(iir & UART_IIR_NO_INT))
serial8250_handle_port(up);
if (is_real_interrupt(up->port.irq))
serial_out(up, UART_IER, ier);
/* Standard timer interval plus 0.2s to keep the port running */
mod_timer(&up->timer,
jiffies + poll_timeout(up->port.timeout) + HZ / 5);
}
如果IRQ線有效 , 先在 IER 中禁用全部中斷 ,等定時器處理函數處理完後,再恢復 IER 中的內容 . 這樣主要是爲了防止會產生髮送緩存區空的中斷 ,流程最後還是會轉入到serial8250_handle_port()中 .這個函數我們在上面已經分析過了 。
八:小結
分析完了這個驅動,我們可以看到 ,專業的開發人員思維是多麼的縝密 , 真是滴水不漏 , 該代碼裏有很多非常精彩的處理,需要細細揣摩。
---------------------
作者:ericson
來源:CSDN
原文:https://blog.csdn.net/yyyks/article/details/7242267
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!