linux移植记录(一) - 串口驱动

来源:互联网 发布:mysql教程linux 编辑:程序博客网 时间:2024/06/09 17:15

    第一次移植Linux系统,新手上路,花了将近一个月的时间,夹杂着失望与喜悦,现在终于可以悠闲的喝点咖啡,写些博客了。

 

设备:交换机

开发板:BCM95836robo

 

由于开发环境比较特殊,u-boot无法使用,使用broadcom公司的CFE。

遇到的第一个问题是串口输出的问题,错误输出信息如下:

                         

        在输出turn off boot console early0后,不在有信息打印,此时实际是启用linux自己的串口驱动ttyS,显然失败了。

 

简要流程:初始调用[注册串口失败] -- > 加载模块[注册设备] -- > 加载驱动[串口注册成功]

 

首分析下串口注册函数register_console()

注意这个结构体struct console console_drivers,在内核中被EXPORT_SYMBOL(即声明为内核中可见)console_drivers为链表结构,记录了注册成功的控制台驱动

struct console {

         char name[16];

         void  (*write)(struct console *, const char *, unsigned);

         int    (*read)(struct console *, char *, unsigned);

         struct tty_driver *(*device)(struct console *, int *);

         void  (*unblank)(void);

         int    (*setup)(struct console *, char *);

         int    (*early_setup)(void);

         short         flags;

         short         index;

         int    cflag;

         void  *data;

         struct       console *next;

};

其中flags参数需要理解,early0串口flags = CON_PRINTBUFFER | CON_BOOT,这里的CON_BOOT使它作为启动时的临时串口输出;ttyS0串口flags = CON_PRINTBUFFER

register_console()首先检查要注册的串口的合法性,然后将其加入console_drivers链表,链表中可能存在多个合法串口设备,此时查看flags标志,flags & CON_ENABLED == 1的即可现在使用的串口,显然只能有一个设备设置此标志,该标志在register_console()函数中被设置。

 

然后按照串口注册流程

1.       linux-2.6.36启动时注册early0串口设备,由于该串口设置了CON_BOOT标志,在register_console()函数中被置位CON_ENABLED,所有输出通过early0输出。

 

2.       然后注册ttyttyS,下面只讲ttyS

注册动作:serial8250_console_init() -- > register_console()

并在接下来进行了如下声明:console_initcall(serial8250_console_init);

上面宏定义在内核做do_initcall()时会被调用,可以理解为控制台注册;完整的serial8250_console_init()函数如下:

static int __init serial8250_console_init(void)

{

         if (nr_uarts > UART_NR)

                   nr_uarts = UART_NR;

 

         serial8250_isa_init_ports();

        

         register_console(&serial8250_console);

         return 0;

}

可见,在register_console前,会调用serial8250_isa_init_ports()函数对serial8250_ports进行初始化,而serial8250_ports的赋值来源于old_serial_port,这个结构体与具体板是没有关系的,通过读取SRIAL_PORT_DFNS的值[定义在相应架构的include/asm/serial.h]

static const struct old_serial_port old_serial_port[] = {

                  SERIAL_PORT_DFNS /* defined in asm/serial.h */

};

这里需要理解下serial8250_portsserial8250_console的联系

                  serial8250_console.data.con = serial8250_ports

 

由于mips架构不是统一的,在serial.h中是空的,所以这次初始的serial8250_portsiobase/membase[important]没有设置,而在稍后register_console()-> serial8250_console_setup()中会检查iobasemembase是否为空,

if (!port->iobase && !port->membase)

                   return -ENODEV;

导致注册失败;tty也一样会注册失败,此时console_drivers仍只有early0一个控制台驱动。

 

3.       然后加载模块,bcm47xxbcm47xx/serial.c定义的uart8250模块被加载,调用初始化函数uart8250_init()

static int __init uart8250_init(void)

{

         int i;

         struct ssb_mipscore *mcore = &(ssb_bcm47xx.mipscore);

 

         memset(&uart8250_data, 0,  sizeof(uart8250_data));

 

         for (i = 0; i < mcore->nr_serial_ports; i++)

         {

                   struct plat_serial8250_port *p = &(uart8250_data[i]);

                   struct ssb_serial_port *ssb_port = &(mcore->serial_ports[i]);

 

                   p->mapbase = (unsigned int) ssb_port->regs;

                   p->membase = (void *) ssb_port->regs;

                   p->irq = ssb_port->irq + 2;

                   p->uartclk = ssb_port->baud_base;

                   p->regshift = ssb_port->reg_shift;

                   p->iotype = UPIO_MEM;

                   p->flags = UPF_BOOT_AUTOCONF | UPF_SHARE_IRQ;

         }

 

         return platform_device_register(&uart8250_device);

}

 

首先根据bcm47xx板的参数ssb_bcm47xx对设备进行初始化,这里比较重要的是mapbase, iotype, flags, type, uartclk

mapbase代表串口的地址,赋值为0xb800 0300 / 0xb800 0400

iotype代表io的类型,赋值为UPIO_MMIO

flags代表标志,赋值为UPF_BOOT_AUTOCONF | UPF_SHARE_IRQ

type代表串口类型,未进行赋值,由于flags设置了UPF_BOOT_AUTOCONF位,会在注册时进行自动的串口类型检测

uartclk代表uart clock

然后通过platform_device_register()注册了两个设备(分别对应于uart0/uart1),具体动作为:platform_device_register() -- > platform_device_add() -- > device_add()将设备添加

 

4.       加载驱动程序,加载8250串口驱动时调用函数serial8250_init()进行初始化。

serial8250_init() -- > serial8250_register_ports() -- > uart_add_one_port() -- >

uart_configure_port(),这个函数会对串口进行配置,在这里会用到先前使用的flags,其中一段代码如下:

if (port->flags & UPF_BOOT_AUTOCONF) {

                   if (!(port->flags & UPF_FIXED_TYPE)) {

                            port->type = PORT_UNKNOWN;

                            flags |= UART_CONFIG_TYPE;

                   }

                   port->ops->config_port(port, flags);

         }

由于设置了UPF_BOOT_AUTOCONF,配置端口,这里主要检测串的类型(即设置iotype字段)。在检测类型前,会做两个端口是否存在的测试。

         代码在运行至第一个测试时,失败[错误所在]

scratch = serial_inp(up, UART_IER);

serial_outp(up, UART_IER, 0);

/*

* 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);

 

scratch3 = serial_inp(up, UART_IER) & 0x0f;

serial_outp(up, UART_IER, scratch);

if (scratch2 != 0 || scratch3 != 0x0F) {

// -- add by yoyo

printk(KERN_INFO "IER test failed (%02x, %02x), scratch = %02x/n",

                       scratch2, scratch3, scratch);

goto out;

}

实际上只是检测写入UART_IER寄存器的值能否被正确读出,但内核启动时的输出  如下:

IER test failed (01, 01), scratch = 01

始终读入的都是0x01,显然是不正确的。

        

如果两个测试通过,然后探测串口的类型,这里用的是IIR寄存器的高2位,对type进行赋值,否则type = PORT_UNKNOWN

         然后进行串口第二次的注册:

if (port->cons && !(port->cons->flags & CON_ENABLED))

                   register_console(port->cons);

         这一次将ttyS0加入的console_drivers链表,设置CON_ENABLED位,并且删除了early0串口,至此,串口加载完成

        

 

第一次控制台注册时失败是正常的,因为串口的地址随板的不同而不同,而此时具体的模块还没加载进来,无法获知正确的串口地址信息。第二次控制台注册成功,因为bcm47xx板模块已被加载,设备的基本参数被赋予了正确的数值,所以此时控制台注册成功。可以确定地址没有错误,那可能是输入输出函数的问题。

 

    回到原点,查看CFE中串口驱动代码,找到输入输出函数,这里是最终的调用:

        #define READREG(sc,r) phys_read8((sc)->uart_base+((r)^0x3))

       #define WRITEREG(sc,r,v) phys_write8((sc)->uart_base+((r)^0x3),(v))

 

    而linux2.6.34内核中串口驱动的输入输出函数:

readb(p->membase + offset );

writeb(value, p->membase + offset);

   对比下,可以发现少了异或0x3,于是作如下修改:

readb(p->membase + (offset ^ 0x3));     

writeb(value, p->membase + (offset ^ 0x3));

   修改后重新编译,运行Linux,串口ttyS启动:

                          

原创粉丝点击