- 論壇徽章:
- 0
|
------------------------------------------
本文系本站原創(chuàng),歡迎轉(zhuǎn)載!
轉(zhuǎn)載請(qǐng)注明出處:http://ericxiao.cublog.cn/
------------------------------------------
一:前言
前一段時(shí)間自己實(shí)踐了一下8250芯片串口驅(qū)動(dòng)的編寫。今天就在此基礎(chǔ)上分析一下linux
kernel自帶的串口驅(qū)動(dòng)。畢竟只有對(duì)比專業(yè)的驅(qū)動(dòng)代碼才能更好的進(jìn)步,同以往一樣,基于linix
kernel2.6.25.相應(yīng)驅(qū)動(dòng)代碼位于:linux-2.6.25/drivers/serial/8250.c。
二:8250串口驅(qū)動(dòng)初始化
相應(yīng)的初始化函數(shù)為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 $Revision: 1.90 $ "
"%d ports, IRQ sharing %sabled\n", nr_uarts,
share_irqs ? "en" : "dis");
for (i = 0; i NR_IRQS; i++)
spin_lock_init(&irq_lists.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;
}
這段代碼涉及到的知識(shí)要求,如platform ,uart等我們?cè)谥岸家呀?jīng)做過詳細(xì)的分析。這里不再重復(fù)。在代碼中UART_NR:表示串口的個(gè)數(shù)。這個(gè)參數(shù)在編譯內(nèi)核的時(shí)候可以自己配置,默認(rèn)為32。
我們按照代碼中的流程一步一步進(jìn)行研究。
1:注冊(cè)u(píng)art_driver.
對(duì)應(yīng)uart-driver的結(jié)構(gòu)為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
從上面可以看出。串口對(duì)應(yīng)的設(shè)備節(jié)點(diǎn)為/dev/ ttyS0 ~ /dev/ ttyS0(UART_NR).設(shè)備節(jié)點(diǎn)號(hào)為(4。64)起始的UART_NR個(gè)節(jié)點(diǎn)..
2:初始化并注冊(cè)platform_device
相關(guān)代碼如下:
serial8250_isa_devs = platform_device_alloc("serial8250", PAT8250_DEV_LEGACY);
platform_device_add(serial8250_isa_devs);
可以看出。serial8250_isa_devs.->name為serial8250。這個(gè)參數(shù)是在匹配platform_device和platform_driver使用的.
3:為uart-driver添加port.
相關(guān)代碼如下:
serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev)
跟進(jìn)這個(gè)函數(shù)看一下:
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;
up->port.dev = dev;
uart_add_one_port(drv, &up->port);
}
}
在這里函數(shù)里,初始化了port.然后將掛添加到uart-driver中。我們還注意到。生成的deivce節(jié)點(diǎn),在sysfs中是位于platform_deivce對(duì)應(yīng)目錄的下面.
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;
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.port;
up->port.irq = irq_canonicalize(old_serial_port.irq);
up->port.uartclk = old_serial_port.baud_base * 16;
up->port.flags = old_serial_port.flags;
up->port.hub6 = old_serial_port.hub6;
up->port.membase = old_serial_port.iomem_base;
up->port.iotype = old_serial_port.io_type;
up->port.regshift = old_serial_port.iomem_reg_shift;
if (share_irqs)
up->port.flags |= UPF_SHARE_IRQ;
}
}
在這里,我們關(guān)注一下注要成員的初始化。Uart_port的各項(xiàng)操作位于serial8250_pops中.iobase irq等成員是從old_serial_por這個(gè)結(jié)構(gòu)中得來的,這個(gè)結(jié)構(gòu)如下所示:
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 */
從上面看到。前兩項(xiàng)對(duì)應(yīng)了com1 com2的各項(xiàng)參數(shù)。如寄存器首始地址,Irq號(hào)等。后面兩項(xiàng)不太清楚。
在上面的代碼中,我們看到了uart_port各項(xiàng)成員的初始化。在后面很多操作中需要用到這個(gè)成員。我們等分析相關(guān)部份的時(shí)候,再到這個(gè)地方來看相關(guān)成員的值。
4:注冊(cè)platform_driver
相關(guān)代碼如下:
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,
},
}
為了以后把分析集中到具體的驅(qū)動(dòng)部份.我們先把這個(gè)platform_driver引會(huì)的事件講述完.
經(jīng)過前面有關(guān)platform的分析我們知道.這個(gè)platform的name為” serial8250”.剛好跟前面注冊(cè)的platform_device相匹配.會(huì)調(diào)用platform_driver-> probe.在這里,對(duì)應(yīng)的接口為:
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;
}
從上述代碼可以看出.會(huì)將dev->dev.platform_data所代表的port添加到uart_driver中.這個(gè)dev->dev.platform_data究竟代表什么.我們?cè)诳吹降臅r(shí)候再來研究它.
現(xiàn)在,我們把精力集中到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.port;
up->port.irq = irq_canonicalize(old_serial_port.irq);
up->port.uartclk = old_serial_port.baud_base * 16;
up->port.flags = old_serial_port.flags;
up->port.hub6 = old_serial_port.hub6;
up->port.membase = old_serial_port.iomem_base;
up->port.iotype = old_serial_port.io_type;
up->port.regshift = old_serial_port.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 | ASYNC_SKIP_TEST)
#define STD_COM4_FLAGS ASYNC_BOOT_AUTOCONF
#endif
從這里看到,不管是否自己探測(cè)IRQ,都會(huì)定義ASYNC_BOOT_AUTOCONF.這樣,在uart_add_one_port()的時(shí)候.就會(huì)進(jìn)入到port->config_port來配置端口.在8250中,對(duì)應(yīng)的接口為: 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的相關(guān)參數(shù)會(huì)從old_serial_port中取得.而old_serial_port中又沒有定義
port->iotype和port-> regshift.也就是說對(duì)應(yīng)這兩項(xiàng)全為0.而
#define UPIO_PORT (0)
即表示是要操作I/O端口.
自己閱讀這兩個(gè)函數(shù)代表.會(huì)發(fā)現(xiàn)在serial8250_request_rsa_resource()中是會(huì)返回失敗的.
另外,在uart_add_one_port()在進(jìn)行端口匹配時(shí),會(huì)先置flags為UART_CONFIG_TYPE.
這樣,在本次操作中, if (flags & UART_CONFIG_TYPE)是會(huì)滿足的.相應(yīng)的就會(huì)進(jìn)入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;
}
}
在這里,先對(duì)8250是否存在做一個(gè)簡(jiǎn)單的判斷.先將IER中的值取得,這樣可以在測(cè)試之后恢復(fù)IER中的值.然后往IER中寫放0.再將IER中的值取
出.又往IER中寫入0xOF.然后再將IER中的值取出.最后將IER中的值恢復(fù)到原值.這樣就可以根據(jù)寫入的值和讀出的值是否相等來判斷該寄存器是否
存在.
save_mcr = serial_in(up, UART_MCR);
save_lcr = serial_in(up, UART_LCR);
在這里,先將MCR和LCR中的值取出.因?yàn)樵诤竺娴牟僮髦袝?huì)使用這兩個(gè)寄存器.方便使用完了恢復(fù)
/*
* 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的自檢位置位,并允許向中斷控制器產(chǎn)生中斷.而且產(chǎn)生RTS信號(hào).這樣MSR寄存器應(yīng)該可以檢測(cè)到這個(gè)信號(hào).如果沒有檢測(cè)到.自測(cè)失敗!MCR寄存器已經(jīng)操作完了,恢復(fù)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 == 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);
}
最后,復(fù)位串口控制器
我們假設(shè)使用的是8250串口芯片.在芯片類型判斷的時(shí)候就會(huì)進(jìn)入autoconfig_8250().代碼如下:
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);
}
如果定義了自己控測(cè)IRQ號(hào)(CONFIG_SERIAL_8250_DETECT_IRQ).一般情況下,編譯內(nèi)核的時(shí)候一般都將其賦值為
CONFIG_SERIAL_8250_DETECT_IRQ = y.此時(shí)就會(huì)進(jìn)入autoconfig_irq().代碼如下:
本文來自ChinaUnix博客,如果查看原文請(qǐng)點(diǎn):http://blog.chinaunix.net/u3/92500/showart_2159902.html |
|