linux设备驱动之8250串口驱动

linux设备驱动之8250串口驱动
linux设备驱动之8250串口驱动

linux设备驱动之8250串口驱动

一:前言

前一段时间自己实践了一下8250芯片串口驱动的编写。今天就在此基础上分析一下linux kernel自带的串口驱动。毕竟只有对比专业的驱动代码才能更好的进步,同以往一样,基于linix 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 $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[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/ ttyS0(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 | ASYNC_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) {

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

Linux驱动程序工作原理简介

Linux驱动程序工作原理简介 一、linux驱动程序的数据结构 (1) 二、设备节点如何产生? (2) 三、应用程序是如何访问设备驱动程序的? (2) 四、为什么要有设备文件系统? (3) 五、设备文件系统如何实现? (4) 六、如何使用设备文件系统? (4) 七、具体设备驱动程序分析 (5) 1、驱动程序初始化时,要注册设备节点,创建子设备文件 (5) 2、驱动程序卸载时要注销设备节点,删除设备文件 (7) 参考书目 (8) 一、linux驱动程序的数据结构 设备驱动程序实质上是提供一组供应用程序操作设备的接口函数。 各种设备由于功能不同,驱动程序提供的函数接口也不相同,但linux为了能够统一管理,规定了linux下设备驱动程序必须使用统一的接口函数file_operations 。 所以,一种设备的驱动程序主要内容就是提供这样的一组file_operations 接口函数。 那么,linux是如何管理种类繁多的设备驱动程序呢? linux下设备大体分为块设备和字符设备两类。 内核中用2个全局数组存放这2类驱动程序。 #define MAX_CHRDEV 255 #define MAX_BLKDEV 255 struct device_struct { const char * name; struct file_operations * fops; }; static struct device_struct chrdevs[MAX_CHRDEV]; static struct { const char *name; struct block_device_operations *bdops; } blkdevs[MAX_BLKDEV]; //此处说明一下,struct block_device_operations是块设备驱动程序内部的接口函数,上层文件系统还是通过struct file_operations访问的。

Linux串口(serial、uart)驱动程序设计

Linux串口(serial、uart)驱动程序设计 https://www.360docs.net/doc/7411362331.html,/space.php?uid=23089249&do=blog&id=34481 一、核心数据结构 串口驱动有3个核心数据结构,它们都定义在<#include linux/serial_core.h> 1、uart_driver uart_driver包含了串口设备名、串口驱动名、主次设备号、串口控制台(可选)等信息,还封装了tty_driver(底层串口驱动无需关心tty_driver)。 struct uart_driver { struct module *owner;/* 拥有该uart_driver的模块,一般为THIS_MODULE */ const char*driver_name;/* 串口驱动名,串口设备文件名以驱动名为基础 */ const char*dev_name;/* 串口设备名*/ int major;/* 主设备号*/ int minor;/* 次设备号*/ int nr;/* 该uart_driver支持的串口个数(最大) */ struct console *cons;/* 其对应的console.若该uart_driver支持serial console, 否则为NULL */ /* * 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; }; 2、uart_port uart_port用于描述串口端口的I/O端口或I/O内存地址、FIFO大小、端口类型、串口时钟等信息。实际上,一个uart_port实例对应一个串口设备

Linux设备驱动程序举例

Linux设备驱动程序设计实例2007-03-03 23:09 Linux系统中,设备驱动程序是操作系统内核的重要组成部分,在与硬件设备之间 建立了标准的抽象接口。通过这个接口,用户可以像处理普通文件一样,对硬件设 备进行打开(open)、关闭(close)、读写(read/write)等操作。通过分析和设计设 备驱动程序,可以深入理解Linux系统和进行系统开发。本文通过一个简单的例子 来说明设备驱动程序的设计。 1、程序清单 //MyDev.c 2000年2月7日编写 #ifndef __KERNEL__ #define __KERNEL__//按内核模块编译 #endif #ifndef MODULE #define MODULE//设备驱动程序模块编译 #endif #define DEVICE_NAME "MyDev" #define OPENSPK 1 #define CLOSESPK 2 //必要的头文件 #include //同kernel.h,最基本的内核模块头文件 #include //同module.h,最基本的内核模块头文件 #include //这里包含了进行正确性检查的宏 #include //文件系统所必需的头文件 #include //这里包含了内核空间与用户空间进行数据交换时的函数宏 #include //I/O访问 int my_major=0; //主设备号 static int Device_Open=0; static char Message[]="This is from device driver"; char *Message_Ptr; int my_open(struct inode *inode, struct file *file) {//每当应用程序用open打开设备时,此函数被调用 printk ("\ndevice_open(%p,%p)\n", inode, file); if (Device_Open) return -EBUSY;//同时只能由一个应用程序打开 Device_Open++; MOD_INC_USE_COUNT;//设备打开期间禁止卸载 return 0; } static void my_release(struct inode *inode, struct file *file)

linux UART串口驱动开发文档

linux UART串口驱动开发文档 w83697/w83977 super I/O串口驱动开发 内容简介: 介绍了Linux下的串口驱动的设计层次及接口, 并指出串口与TTY终端之间的关联层次(串口可作TTY终端使用), 以及Linux下的中断处理机制/中断共享机制, 还有串口缓冲机制当中涉及的软中断机制; 其中有关w83697/w83977 IC方面的知识, 具体参考相关手册, 对串口的配置寄存器有详细介绍, 本文不再进行说明. 目录索引: 一. Linux的串口接口及层次. 二. Linux的中断机制及中断共享机制. 三. Linux的软中断机制. 四. TTY与串口的具体关联. 一. Linux的串口接口及层次. 串口是使用已经非常广的设备了, 因此在linux下面的支持已经很完善了, 具有统一的编程接口, 驱动开发者所要完整的工作就是针对不同的串口IC来做完成相应的配置宏, 这此配置宏包括读与写, 中断打开与关闭(如传送与接收中断), 接收状态处理, 有FIFO时还要处理FIFO的状态. 如下我们就首先切入这一部分, 具体了解一下与硬件串口IC相关的部分在驱动中的处理, 这一部分可以说是串口驱动中的最基础部分, 直接与硬件打交道, 完成最底层具体的串口数据传输. 1. 串口硬件资源的处理. W83697及W83977在ep93xx板子上的映射的硬件物理空间如下: W83697: 0x20000000起1K空间. W83977: 0x30000000起1K空间. 因为串口设备的特殊性, 可以当作终端使用, 但是终端的使用在内核还未完全初始化之前(关于串口与终端的关联及层次在第四节中详细), 此时还没有通过mem_init()建立内核的虚存管理机制, 所以不能通过ioreamp来进行物理内存到虚存的映射(物理内存必须由内核映射成系统管理的虚拟内存后才能进行读写访问), 这与先前所讲的framebuffer的物理内存映射是不同的, 具体原因如下: √终端在注册并使用的调用路径如下: start_kernel→console_init→uart_console_init→ep93xxuart_console_init→register_conso

linux串口测试程序

linux串口测试程序 由于已经完成了第一个HELLO程序,标志着整个编译环境已经没有问题了,下来准备做一下串口测试程序。由于串口驱动开发板已经作好了,所以就作一个Linux串口测试工具简单的数据收发看看。 Linux串口测试工具网上常见的版本都看起来比较烦琐,下面是一个简单一点的,这个程序功能是收到10个字节后会发前7个字节,如果所发的数据的第一个字节是9则退出。 #include #include #include #include #include #include #include #include #define BAUDRATE B9600 #define MODEMDEVICE "/dev/ttyUSB1" int main() { int fd,c=0,res;struct termios oldtio,newtio;//intch;static char s1[10],buf[10];printf("start ……\n");/*打开PC的COM1口*/ fd = open(MODEMDEVICE,O_RDWR|O_NOCTTY);if (fd < 0) { perror(MODEMDEVICE);exit(1);} printf("open……\n");/*将旧的通讯参数存入oldtio结构*/ tcgetattr(fd,&oldtio);/*初始化新的newtio */ bzero(&newtio,sizeof(newtio));/*8N1*/ newtio.c_cflag = BAUDRATE|CS8|CLOCAL|CREAD;newtio.c_iflag = IGNPAR;newtio.c_oflag = 0;/*正常模式*/ /*newtio.c_lflag = ICANON;*/ /*非正常模式*/ newtio.c_lflag = 0;newtio.c_cc[VTIME] = 0;newtio.c_cc[VMIN] = 10; tcflush(fd,TCIFLUSH);/*新的temios作为通讯端口参数*/ tcsetattr(fd,TCSANOW,&newtio);printf("writing……\n"); while(1) { //printf("read……\n");res = read(fd,buf,10);//res = read(fd,s1,10);//strcat(buf,s1);// res = write(fd,buf,7);printf("buf = %s\n",buf);if(buf[0]==9) break;} printf("close……\n");close(fd);/*还原旧参数*/ tcsetattr(fd,TCSANOW,&oldtio);return 0;} 还有一点要注意,就是Linux串口测试工具串口有两种工作模式,即正规模式和非正规模式,如果习惯在串口调试器中用16进制发送,此时串口应该为非正规模式才行。 下面是这两种模式的说明Linux串口测试工具正规模式(CANONICAL或者COOKED) 此模式下,终端设备会处理特殊字符,并且数据传输是一次一行的方式,既按回车后才开始发送和接收数据。例如LINUX的SHELL. Linux串口测试工具非正规模式(NON-CANONICAL

linux串口编程参数配置详解

linux串口编程参数配置详解 1.linux串口编程需要的头文件 #include //标准输入输出定义 #include //标准函数库定义 #include //Unix标准函数定义 #include #include #include //文件控制定义 #include //POSIX中断控制定义 #include //错误号定义 2.打开串口 串口位于/dev中,可作为标准文件的形式打开,其中: 串口1 /dev/ttyS0 串口2 /dev/ttyS1 代码如下: int fd; fd = open(“/dev/ttyS0”, O_RDWR); if(fd == -1) { Perror(“串口1打开失败!”); } //else //fcntl(fd, F_SETFL, FNDELAY); 除了使用O_RDWR标志之外,通常还会使用O_NOCTTY和O_NDELAY这两个标志。 O_NOCTTY:告诉Unix这个程序不想成为“控制终端”控制的程序,不说明这

个标志的话,任何输入都会影响你的程序。 O_NDELAY:告诉Unix这个程序不关心DCD信号线状态,即其他端口是否运行,不说明这个标志的话,该程序就会在DCD信号线为低电平时停止。 3.设置波特率 最基本的串口设置包括波特率、校验位和停止位设置,且串口设置主要使用termios.h头文件中定义的termios结构,如下: struct termios { tcflag_t c_iflag; //输入模式标志 tcflag_t c_oflag; //输出模式标志 tcflag_t c_cflag; //控制模式标志 tcflag_t c_lflag; //本地模式标志 cc_t c_line; //line discipline cc_t c_cc[NCC]; //control characters } 代码如下: int speed_arr[] = { B38400, B19200, B9600, B4800, B2400, B1200, B300, B384 00, B19200, B9600, B4800, B2400, B1200, B300, }; int name_arr[] = {38400, 19200, 9600, 4800, 2400, 1200, 300, 38400, 19200, 9 600, 4800, 2400, 1200, 300, }; void SetSpeed(int fd, int speed) { int i; struct termios Opt; //定义termios结构 if(tcgetattr(fd, &Opt) != 0) { perror(“tcgetattr fd”); return; }

Linux设备驱动程序学习(18)-USB 驱动程序(三)

Linux设备驱动程序学习(18)-USB 驱动程序(三) (2009-07-14 11:45) 分类:Linux设备驱动程序 USB urb (USB request block) 内核使用2.6.29.4 USB 设备驱动代码通过urb和所有的 USB 设备通讯。urb用 struct urb 结构描述(include/linux/usb.h )。 urb以一种异步的方式同一个特定USB设备的特定端点发送或接受数据。一个USB 设备驱动可根据驱动的需要,分配多个 urb 给一个端点或重用单个 urb 给多个不同的端点。设备中的每个端点都处理一个 urb 队列, 所以多个 urb 可在队列清空之前被发送到相同的端点。 一个 urb 的典型生命循环如下: (1)被创建; (2)被分配给一个特定 USB 设备的特定端点; (3)被提交给 USB 核心; (4)被 USB 核心提交给特定设备的特定 USB 主机控制器驱动; (5)被 USB 主机控制器驱动处理, 并传送到设备; (6)以上操作完成后,USB主机控制器驱动通知 USB 设备驱动。 urb 也可被提交它的驱动在任何时间取消;如果设备被移除,urb 可以被USB 核心取消。urb 被动态创建并包含一个内部引用计数,使它们可以在最后一个用户释放它们时被自动释放。 struct urb

struct list_head urb_list;/* list head for use by the urb's * current owner */ struct list_head anchor_list;/* the URB may be anchored */ struct usb_anchor *anchor; struct usb_device *dev;/* 指向这个 urb 要发送的目标 struct usb_device 的指针,这个变量必须在这个 urb 被发送到 USB 核心之前被USB 驱动初始化.*/ struct usb_host_endpoint *ep;/* (internal) pointer to endpoint */ unsigned int pipe;/* 这个 urb 所要发送到的特定struct usb_device 的端点消息,这个变量必须在这个 urb 被发送到 USB 核心之前被 USB 驱动初始化.必须由下面的函数生成*/ int status;/*当 urb开始由 USB 核心处理或处理结束, 这个变量被设置为 urb 的当前状态. USB 驱动可安全访问这个变量的唯一时间是在 urb 结束处理例程函数中. 这个限制是为防止竞态. 对于等时 urb, 在这个变量中成功值(0)只表示这个 urb 是否已被去链. 为获得等时 urb 的详细状态, 应当检查 iso_frame_desc 变量. */ unsigned int transfer_flags;/* 传输设置*/ void*transfer_buffer;/* 指向用于发送数据到设备(OUT urb)或者从设备接收数据(IN urb)的缓冲区指针。为了主机控制器驱动正确访问这个缓冲, 它必须使用 kmalloc 调用来创建, 不是在堆栈或者静态内存中。对控制端点, 这个缓冲区用于数据中转*/ dma_addr_t transfer_dma;/* 用于以 DMA 方式传送数据到 USB 设备的缓冲区*/ int transfer_buffer_length;/* transfer_buffer 或者 transfer_dma 变量指向的缓冲区大小。如果这是 0, 传送缓冲没有被 USB 核心所使用。对于一个 OUT 端点, 如果这个端点大小比这个变量指定的值小, 对这个USB 设备的传输将被分成更小的块,以正确地传送数据。这种大的传送以连续的 USB 帧进行。在一个 urb 中提交一个大块数据, 并且使 USB 主机控制器去划分为更小的块, 比以连续地顺序发送小缓冲的速度快得多*/

linux驱动程序的编写

linux驱动程序的编写 一、实验目的 1.掌握linux驱动程序的编写方法 2.掌握驱动程序动态模块的调试方法 3.掌握驱动程序填加到内核的方法 二、实验内容 1. 学习linux驱动程序的编写流程 2. 学习驱动程序动态模块的调试方法 3. 学习驱动程序填加到内核的流程 三、实验设备 PentiumII以上的PC机,LINUX操作系统,EL-ARM860实验箱 四、linux的驱动程序的编写 嵌入式应用对成本和实时性比较敏感,而对linux的应用主要体现在对硬件的驱动程序的编写和上层应用程序的开发上。 嵌入式linux驱动程序的基本结构和标准Linux的结构基本一致,也支持模块化模式,所以,大部分驱动程序编成模块化形式,而且,要求可以在不同的体系结构上安装。linux是可以支持模块化模式的,但由于嵌入式应用是针对具体的应用,所以,一般不采用该模式,而是把驱动程序直接编译进内核之中。但是这种模式是调试驱动模块的极佳方法。 系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以像操作普通文件一样对硬件设备进行操作。同时,设备驱动程序是内核的一部分,它完成以下的功能:对设备初始化和释放;把数据从内核传送到硬件和从硬件读取数据;读取应用程序传送给设备文件的数据和回送应用程序请求的数据;检测和处理设备出现的错误。在linux操作系统下有字符设备和块设备,网络设备三类主要的设备文件类型。 字符设备和块设备的主要区别是:在对字符设备发出读写请求时,实际的硬件I/O一般就紧接着发生了;块设备利用一块系统内存作为缓冲区,当用户进程对设备请求满足用户要求时,就返回请求的数据。块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间来等待。 1 字符设备驱动结构 Linux字符设备驱动的关键数据结构是cdev和file_operations结构体。

从零开始搭建Linux驱动开发环境

参考: 韦东山视频第10课第一节内核启动流程分析之编译体验 第11课第三节构建根文件系统之busybox 第11课第四节构建根文件系统之构建根文件系统韦东山书籍《嵌入式linux应用开发完全手册》 其他《linux设备驱动程序》第三版 平台: JZ2440、mini2440或TQ2440 交叉网线和miniUSB PC机(windows系统和Vmware下的ubuntu12.04) 一、交叉编译环境的选型 具体的安装交叉编译工具,网上很多资料都有,我的那篇《arm-linux- gcc交叉环境相关知识》也有介绍,这里我只是想提示大家:构建跟文件系统中所用到的lib库一定要是本系统Ubuntu中的交叉编译环境arm-linux- gcc中的。即如果电脑ubuntu中的交叉编译环境为arm-linux-

二、主机、开发板和虚拟机要三者互通 w IP v2.0》一文中有详细的操作步骤,不再赘述。 linux 2.6.22.6_jz2440.patch组合而来,具体操作: 1. 解压缩内核和其补丁包 tar xjvf linux-2.6.22.6.tar.bz2 # 解压内核 tar xjvf linux-2.6.22.6_jz2440.tar.bz2 # 解压补丁

cd linux_2.6.22.6 patch –p1 < ../linux-2.6.22.6_jz2440.patch 3. 配置 在内核目录下执行make 2410_defconfig生成配置菜单,至于怎么配置,《嵌入式linux应用开发完全手册》有详细介绍。 4. 生成uImage make uImage 四、移植busybox 在我们的根文件系统中的/bin和/sbin目录下有各种命令的应用程序,而这些程序在嵌入式系统中都是通过busybox来构建的,每一个命令实际上都是一个指向bu sybox的链接,busybox通过传入的参数来决定进行何种命令操作。 1)配置busybox 解压busybox-1.7.0,然后进入该目录,使用make menuconfig进行配置。这里我们这配置两项 一是在编译选项选择动态库编译,当然你也可以选择静态,不过那样构建的根文件系统会比动态编译的的大。 ->Busybox Settings ->Build Options

linux设备驱动,tty串口编程

linux设备驱动,tty串口编程2011-12-04 08:56:33 分类:LINUX XC2440开发板上已经含有S3C2440的3个串口驱动,我们只要知道各个串口的设备名称就可以了, 204 s3c2410_serial ,204是串口的主设备号。s3c2410_serial是设备名称,在 dev目录下 ls 一下就可以发现 ptyd0 s3c2410_serial0 ttysa ptyd1 s3c2410_serial1 ttysb ptyd2 s3c2410_serial2 ttysc s3c2410_serial0,s3c2410_serial1,s3c2410_serial2 分别是串口1、2、3的设备名称 下面是测试源码,打开串口1、2,程序执行后,串口1的波特率变为9600,这时候你的串口 终端就没有反应了(串口1波特率默认115200),把终端软件串口1 波特率改为9600后, 连接终端,回车一下,然后输入几个‘1’后,画面如上图。 这时用telnet工具登陆开发板,执行ps 查看现有运行的程序,找到tty [root@XC2440 /root]# ps PID USER TIME COMMAND 1 root 0:04 init 2 root 0:00 [kthreadd] 3 root 0:00 [ksoftirqd/0] 5 root 0:00 [kworker/u:0] 6 root 0:00 [khelper] 7 root 0:00 [kworker/u:1] 10 root 0:00 [netns] 236 root 0:00 [sync_supers] 238 root 0:00 [bdi-default] 240 root 0:00 [kblockd] 249 root 0:00 [khubd] 252 root 0:00 [kseriod]

Linux下串口编程所要知道的那些事

Linux下串口编程所要知道的那些事 [日期:2011- 来源:csdn 作者:tiger-john 1. 波特率 1> 表示每秒传输的比特数。 2> 说明:若波特率为115200,它表示什么呢? ? 对于发送断,即每秒钟发送115200bit。 ? 对于接收端,115200波特率意味着串口通信在数据线上的采样率为115200HZ. 注:波特率和距离之间成反比,距离相隔很近的设备之间可以实现高波特率通信。 2. 数据位 1> 表示通信中实际数据位的参数。在计算机发送的数据包中,实际的数据往往不会是8位。 2> 说明:在串口通信中,可以选择5,6,7,8位。设定数据位时,主要考虑所要传输的数据内容。 3> 事例:如果要传输的是标准的ASCII码。那么又该如何设定数据位呢? ? 由于ASCII码的范围是0~127,因此设定数据位为7就OK了。 ? 若为扩展ASCII码,其范围是0~255,必须使用8位。 注:7位或8位数据中不仅仅是数据,还包括开始/停止位,数据位以及奇偶校验位等。 3. 奇偶校验位 1> 作用:该位用于串口通信中的简单检验错。 2> 类型:主要有偶校验,奇校验,标记,空格的方式 在ARM7(LPC2200)中,只有偶校验,奇校验两种方式。 3> 方法:如何进行校验? ? 奇偶校验是通过统计数据中高位或低位的个数来实现校验的。 ? 标记,空格并不是真正校验错误的,只是通过简单的置位来实现对

数据的检测。https://www.360docs.net/doc/7411362331.html,通过置位方式,可以判断出是否存在噪声干扰数据通信或数据传输,以及是否存在不同步的现象 4. 停止位 1> 作用:停止位用于标志该数据包数据结束,可以取1位,1.5位或2位。 在ARM7(lpc2200中)停止位可以取1位,2位或不取 2> 说明: ? 停止位不仅仅用于数据包的传输结束标志,还提供了计算机之间校正同步时钟的机会。 ? 用于停止位的位数越多,不同时钟同步的容忍程序越大。 ? 但是由于停止位占用了数据空间,过多的停止位将导致数据传输速度的下降。 5. 数据流控制 1> 通过串口传输数据时,由于计算机之间处理速度或其他因素的影响,会造成丢失数据的现象。 2> 作用:数据流控制用于解决上面的问题,通过控制发送数据的速度,确保数据不会出现丢失。 3> 类型:数据流控制可以分为软件流控制(Xon/Xoff)和硬件流控制,当然你可以选择不使用数据流控制。 ? 软件流控制使用特殊的字符作为启动或停止的标志 ? 硬件流控制通过使用硬件信号(CTR/RTS)来实现。 注:使用硬件流控制时,在接收端准备好接收数据后,设为CTS为1,否则CTS为0。同样,如果发送端准备好要发送数据时,则设定RTS为1;如果还未准备好,设置CTS为0. 二. Linux串口下编程所要考虑的问题 1. Linux下编写串口程序的思想 看图:

linux设备驱动之8250串口驱动

linux设备驱动之8250串口驱动 一:前言 前一段时间自己实践了一下8250芯片串口驱动的编写。今天就在此基础上分析一下linux kernel自带的串口驱动。毕竟只有对比专业的驱动代码才能更好的进步,同以往一样,基于linix 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 $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[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);

Linux设备驱动程序简介

第一章Linux设备驱动程序简介 Linux Kernel 系统架构图 一、驱动程序的特点 ?是应用和硬件设备之间的一个软件层。 ?这个软件层一般在内核中实现 ?设备驱动程序的作用在于提供机制,而不是提供策略,编写访问硬件的内核代码时不要给用户强加任何策略 o机制:驱动程序能实现什么功能。 o策略:用户如何使用这些功能。 二、设备驱动分类和内核模块 ?设备驱动类型。Linux 系统将设备驱动分成三种类型 o字符设备 o块设备 o网络设备 ?内核模块:内核模块是内核提供的一种可以动态加载功能单元来扩展内核功能的机制,类似于软件中的插件机制。这种功能单元叫内核模块。 ?通常为每个驱动创建一个不同的模块,而不在一个模块中实现多个设备驱动,从而实现良好的伸缩性和扩展性。 三、字符设备 ?字符设备是个能够象字节流<比如文件)一样访问的设备,由字符设备驱动程序来实现这种特性。通过/dev下的字符设备文件来访问。字符设备驱动程序通常至少需要实现 open、close、read 和 write 等系统调用 所对应的对该硬件进行操作的功能函数。 ?应用程序调用system call<系统调用),例如:read、write,将会导致操作系统执行上层功能组件的代码,这些代码会处理内核的一些内部 事务,为操作硬件做好准备,然后就会调用驱动程序中实现的对硬件进 行物理操作的函数,从而完成对硬件的驱动,然后返回操作系统上层功 能组件的代码,做好内核内部的善后事务,最后返回应用程序。 ?由于应用程序必须使用/dev目录下的设备文件<参见open调用的第1个参数),所以该设备文件必须事先创建。谁创建设备文件呢? ?大多数字符设备是个只能顺序访问的数据通道,不能前后移动访问指针,这点和文件不同。比如串口驱动,只能顺序的读写设备。然而,也 存在和数据区或者文件特性类似的字符设备,访问它们时可前后移动访

linux驱动程序进入内核

ARM-uClinux下编写加载驱动程序详细过程 本文主要介绍在uClinux下,通过加载模块的方式调试IO控制蜂鸣器的驱动程序。实验过程与上篇文章所讲的过程基本相似,更多注重细节及注意事项。 本文适合学习ARM—Linux的初学者。 //================================================================== 硬件平台:MagicARM2200教学试验开发平台(LPC2290) Linux version 2.4.24,gcc version 2.95.3 电路连接:P0.7——蜂鸣器,低电平发声。 实验条件:uClinux内核已经下载到开发板上,能够正常运行;与宿主机相连的网络、串口连接正常。 //================================================================== 编写蜂鸣器的驱动程序相对来说容易实现,不需要处理中断等繁琐的过程,本文以蜂鸣器的驱动程序为例,详细说明模块化驱动程序设计的主要过程和注意事项。 一、编写驱动程序 驱动程序的编写与上文所说的编写过程基本相同,这里再详细说明一下。 //========================================== //蜂鸣器驱动程序:beep.c文件 //------------------------------------------------------------------- #include /*模块相关*/ #include /*内核相关*/ #include /*linux定义类型*/ #include /*文件系统 file_opertions 结构体定义*/ #include /*出错信息*/ /*PINSEL0 注意:低2位是UART0复用口,不要改动*/ #define PINSEL0 (*((volatile unsigned*) 0xE002C000)) /*P0口控制寄存器*/ #define IO0PIN (*((volatile unsigned*) 0xE0028000))

Linux_终端控制台体系及串口驱动分析

Linux终端控制台体系及串口驱动分析 数据通信的基本方式可分为并行通信与串行通信两种: 并行通信:利用多条数据线路将数据的各位同时传送。它的特点是传输速度快,适用于短距离通信。 串行通信:利用一条数据线将数据一位位顺序传送。特点是通信线路简单,利用简单的线缆就可实现通信,低成本,适用于远距离通信。 异步通信以一个字符为传输单位,通信中的两个字符间的时间间隔是不固定的,然而同一个字符中的两个相邻位之间的时间间隔是固定的。 通信协议:是指双方约定的一些规则。在使用异步串口传送一个字符信息时,对数据格式有如下规定:规定有空闲位、起始位、资料位、奇偶校验位、停止位。 起始位:先发一个逻辑“0”信号,表示传输字符的开始 数据位:紧接在起始位之后。数据位的个数可以是4、5、6、7、8,从最低位开始传送,靠时钟定位。 奇偶校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此校验数据传送的正确性。 停止位:它是一个字符数据的结束标志。 空闲位:处于逻辑“1”状态,表示当前线路上没有数据传送。 波特率:是衡量数据传送速率的指针。表述每秒钟传送的二进制位数。 注:异步通信是按字符传输的,接收设备在收到起始信号之后在一个字符的传输时间内能和发送设备保持同步就能正确接收。 传送方式:单工方式、半双工方式、全双工方式 终端概述: 在Linux中,TTY(终端)是一类字符设备的统称,它包括了3种类型:控制台、串口和伪终端。 控制台:供内核使用的终端为控制台。控制台在Linux启动时,通过命令console=…指定,如果没有指定控制台,系统把第一个注册的终端(tty)作为控制台。 1、控制台是一个虚拟的终端,它必须映射到真正的终端上。 2、控制台可以简单的理解为printk输出的地方 3、控制台是个只输出的设备,功能很简单,只能在内核中访问。 伪终端: 伪终端设备是一种特殊的终端设备,由主-从两个成对的设备构成,当打开主设备时,对应的从设备随之打开,形成连接状态。输入到主设备的数据成为从设备的输出,输入到从设备的数据成为主设备的输出,形成双向管道。伪终端设备通常用于远程登录服务器来建立网络和终端的关联。当通过telnet远程登录到另一台主机时,telnet进程与远程主机的telnet服务器相连接。telnet服务器使用某个主设备并通过对应的从设备与telnet进程相互通信。

linux串口触摸屏设计总结

Linux serial touch 设计总结 概述: 最近在做嵌入式linux下串口触摸屏设计,遇到一些问题,经过查找资料和请教同事,总算把问题解决了,事后有把linux相关的内核代码仔细看了一遍,为了有点成果,特别写了个总结。如有任何问题请联系yxj_5421@https://www.360docs.net/doc/7411362331.html,,转载请标明出处。 系统资源: Linux:2.6.36 UI:QT+TSLIB 硬件资源不关心 设计方法: 有两种实现途径。 1、是将要使用的串口单独拿出来,作为一个platform总线设备实现,在嵌入式平 台mach文件里面,加上串口中断号和寄存器首地址,然后将这个串口注册成 一个platform总线设备。在驱动probe函数里面需要得到这个串口中断号以及 寄存器映射地址,通过寄存器映射地址设置串口波特率,数据位,停止位等, 通过中断号注册中断等,然后调用input_register_device注册一个input设备。 在中断里面得到外面触摸屏的数据,然后根据input touch协议上报触摸数据。 这种方法实现简单明了,不需要和linux的tty,serio等打交道。但是要求知道 串口硬件spec,比如寄存器等,而且这个串口就只能给触摸屏使用了,不能作 为tty使用。因为是嵌入式开发,因此很容易知道硬件spec,而且嵌入式平台 一旦确定,那么这个串口肯定就是给触摸屏使用了。因此在嵌入式平台上,推 荐使用这个方法。 2、是将串口作为一个serio总线设备,利用linux内核提供serio总线驱动,通过设 置对应的串口,调用serport提供的函数将串口当做serio总线设备,在驱动里 面需要按照serio总线设备驱动的框架来实现,这方面的例子linux里面有很多, 比如touchright.c,在模块init函数里面调用serio_register_driver注册serio总线 设备驱动,如果serio总线上对应的serio设备存在,就调用connect函数,在 这个函数里面调用input_register_device注册一个input设备。具体驱动不再分 析了,很简单,相信各位都能看的懂。 至此,两种方法都实现了串口触摸屏的驱动,讲到这里是不是就完了,非也,本文的重点还在后面,请看下面分析: 第一种方法只要驱动模块被加载,就会在/dev/input下面创建一个eventx节点,tslib就能访问这个节点,获得触摸坐标,然后送给qt。 第二种方法驱动模块加载后,并没有创建eventx节点,也就是说connect函数没有被调用,按照linux驱动模型来看,就是serio总线上还没有对应的serio设备,因此驱动加载时没有对应的设备,就不会调用connect函数,这时的串口还是作为一个linux tty设备存在。 我遇到的问题就是serio驱动加载了,但是没有创建eventx节点,查找资料也只有一个说是要把tty设置成N_MOUSE,然后读,说的不清楚,也不知道怎么实现,经过自己摸索,终于把问题解决了。 Linux 启动后串口形式:

Linux设备驱动程序说明介绍

Linux设备驱动程序简介 Linux是Unix操作系统的一种变种,在Linux下编写驱动程序的原理和思想完全类似于其他的Unix系统,但它dos或window环境下的驱动程序有很大的区别。在Linux环境下设计驱动程序,思想简洁,操作方便,功能也很强大,但是支持函数少,只能依赖kernel 中的函数,有些常用的操作要自己来编写,而且调试也不方便。本人这几周来为实验室自行研制的一块多媒体卡编制了驱动程序,获得了一些经验,愿与Linux fans共享,有不当之处,请予指正。 以下的一些文字主要来源于khg,johnsonm的Write linux device driver,Brennan's Guide to Inline Assembly,The Linux A-Z,还有清华BBS上的有关device driver的一些资料. 这些资料有的已经过时,有的还有一些错误,我依据自己的试验结果进行了修正. 一、Linux device driver 的概念 系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口.设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作.设备驱动程序是内核的一部分,它完成以下的功能: 1.对设备初始化和释放. 2.把数据从内核传送到硬件和从硬件读取数据. 3.读取应用程序传送给设备文件的数据和回送应用程序请求的数据. 4.检测和处理设备出现的错误. 在Linux操作系统下有两类主要的设备文件类型,一种是字符设备,另一种是块设备.字符设备和块设备的主要区别是:在对字符设备发出读/写请求时,实际的硬件I/O一般就紧接着发生了,块设备则不然,它利用一块系统内存作缓冲区,当用户进程对设备请求能满足用户的要求,就返回请求的数据,如果不能,就调用请求函数来进行实际的I/O操作.块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间来等待. 已经提到,用户进程是通过设备文件来与实际的硬件打交道.每个设备文件都都有其文件属性(c/b),表示是字符设备还蔤强樯璞?另外每个文件都有两个设备号,第一个是主设备号,标识驱动程序,第二个是从设备号,标识使用同一个设备驱动程序的不同的硬件设备,比如有两个软盘,就可以用从设备号来区分他们.设备文件的的主设备号必须与设备驱动程序在登记时申请的主设备号一致,否则用户进程将无法访问到驱动程序. 最后必须提到的是,在用户进程调用驱动程序时,系统进入核心态,这时不再是抢先式调度.也就是说,系统必须在你的驱动程序的子函数返回后才能进行其他的工作.如果你的驱动程序陷入死循环,不幸的是你只有重新启动机器了,然后就是漫长的fsck. 读/写时,它首先察看缓冲区的内容,如果缓冲区的数据 如何编写Linux操作系统下的设备驱动程序 二、实例剖析 我们来写一个最简单的字符设备驱动程序。虽然它什么也不做,但是通过它可以了解Linux的设备驱动程序的工作原理.把下面的C代码输入机器,你就会获得一个真正的设备驱动程序.不过我的kernel是2.0.34,在低版本的kernel上可能会出现问题,我还没测试过. [code]#define __NO_VERSION__

相关文档
最新文档