pmon 的 printf 初始化及调用流分析

1  __init()函数的解释:

Pmon中有三类constructor函数,他们都是静态函数,此类函数都有一个共性就是受__attribute__((constructor))属性修饰,例如 init_fs 函数(在gpio,spi,devfs,diskfs,等文件里面都有)

static void init_fs __P((void)) __attribute__ ((constructor));

下面说明三类函数:
        a:命令处理函数,位于pmon/cmds目录,其名称都叫init_cmd(),该目录下几乎每个函数都有。
        b:文件系统初始化函数,位于pmon/fs目录,其名称叫init_fs()或init_xxxfs()。例如init_diskfs、init_fs、init_netfs等。
        c:可执行文件类型初始化函数,位于pmon/loaders目录中exec_*.c文件中,函数叫init_exec()。


刚开始时候调用函数 init_fs :

static void init_fs()
{
        SBD_DISPLAY ("DEVI", CHKPNT_DEVI);
        devinit ();

        /*
         * Install terminal based file system.
         */
        filefs_init(&termfs);

        /*
         * Create the standard i/o files the proper way
         */
        _file[0].valid = 1;
        _file[0].fs = &termfs;
        _file[1].valid = 1;
        _file[1].fs = &termfs;
        _file[2].valid = 1;
        _file[2].fs = &termfs;
        _file[3].valid = 1;
        _file[3].fs = &termfs;
        _file[4].valid = 1;
        _file[4].fs = &termfs;
        _file[5].valid = 1;
        _file[5].fs = &termfs;
        term_open(0, "/dev/tty0", 0, 0); /* stdin */
        term_open(1, "/dev/tty0", 0, 0); /* stdout */
        term_open(2, "/dev/tty0", 0, 0); /* stderr */
        term_open(3, "/dev/tty1", 0, 0); /* kbdin */
        term_open(4, "/dev/tty1", 0, 0); /* vgaout */
        term_open(5, "/dev/tty2", 0, 0); /* vgaout */
}


在上面函数中首先调用 devinit:

int devinit (void)
{
        int i, brate;
        ConfigEntry *q;
        DevEntry *p;
        char dname[10];
        char *s;

        strcpy(dname, "tty_baud");
        for (i = 0; ConfigTable[i].devinfo && i < /* 2 */  DEV_MAX; i++) {
                q = &ConfigTable[i];
                p = &DevTable[i];
                p->txoff = 0;
                p->qsize = q->rxqsize;         /// 接收队列大小
                p->sio = q->devinfo;           /// q devinfo设备信息(COM口基地址)这里赋值到p->sio.
                p->chan = q->chan;
                p->rxoff = 0;
                p->handler = q->handler;       /// 对于串口这个是ns16550
                p->tfunc = 0;
                p->freq = q->freq;             /// NS16550HZ
                p->nopen = 0;
                p->shift = q->shift;

                if (p->chan == 0)
                        (*p->handler) (OP_INIT, p, NULL, q->rxqsize);

                p->rxq = Qcreate (p->qsize);      /// rxqsize
                p->txq = Qcreate (p->qsize);
                if (p->rxq == 0 || p->txq == 0)
                        return (-1);
                /*
                 * program requested baud rate, but fall back to default
                 * if there is a problem
                 */
                /* XXX don't work. env init is not called yet. has to be solved */
                dname[3] = (i < 10) ? i + '0' : i - 10 + 'a';
                if ((s = getenv(dname)) == 0 || (brate = getbaudrate(s)) == 0)
                        brate = q->brate;             //// CONS_BAUD

                if ((brate != q->brate)||(q->flag&1)) {
                        if ((*p->handler)(OP_BAUD, p, NULL, brate)) {
                                brate = q->brate;
                                (void)(*p->handler)(OP_BAUD, p, NULL, brate);
                        }
                }

                p->t.c_ispeed = brate;
                p->t.c_ospeed = brate;
        }
        return (0);
}
其中  ConfigTable 定义如下:

ConfigEntry     ConfigTable[] =
{
#ifdef CONFIG_EJTAG_SERIAL
         { (char *)COM1_BASE_ADDR, 0, ejtag_serial, 256, CONS_BAUD, NS16550HZ},
#else
         { (char *)COM1_BASE_ADDR, 0, ns16550, 256, CONS_BAUD, NS16550HZ},
#endif
#if NMOD_VGACON >0 && NMOD_FRAMEBUFFER >0
        { (char *)1, 0, fbterm, 256, CONS_BAUD, NS16550HZ },
#endif
        { 0 }
};
COM1_BASE_ADDR 表示串口的地址。

ns16550 设备处理的handler ,以后会通过 -> handler 指针 来访问这个函数。

256 表示rxqsize,接收缓冲区队列大小,这个是2的次幂很重要。方便以后队列处理。

CONS_BAUD 设置的波特率。

NS16550HZ  uart设备的频率。

  上面函数的作用就是用设备配置表 ConfigTable 里面的内容来初始化设备表 DevTable。创建队列等系列操作。


然后调用 term_open(x, "/dev/ttyx", 0, 0);  打开若干个设备:

int term_open(int fd, const char *fname, int mode, int perms)
{
        int c, dev;
        char *dname;
        struct TermDev *devp;
        DevEntry *p;

        dname = (char *)fname;
        if (strncmp (dname, "/dev/", 5) == 0)
                dname += 5;

        if (strlen (dname) == 4 && strncmp (dname, "tty", 3) == 0) {
                c = dname[3];
                if (c >= 'a' && c <= 'z')
                        dev = c - 'a' + 10;
                else if (c >= 'A' && c <= 'Z')
                        dev = c - 'A' + 10;
                else if (c >= '0' && c <= '9')
                        dev = c - '0';
                if (dev >= DEV_MAX || DevTable[dev].rxq == 0)
                        return  -1;

                devp = (struct TermDev *)malloc(sizeof(struct TermDev));
                if (devp == NULL) {
                        errno = ENOMEM;
                        return -1;
                }
                devp->dev = dev;
                _file[fd].data = (void *)devp;
                p = &DevTable[dev];
                if(p->handler){
                (*p->handler) (OP_OPEN, p, NULL, 0);
                if (p->nopen++ == 0)
                        term_ioctl (fd, SETSANE);
                        }
        }
        else {
                return -1;
        }
        return fd;
}
当打开 /dev/ttyx 这里的x一般为数字。表示打开 DevTable 表里面的第 x个设备 (dev =x ) (x从0开始)

_file为一个结构体数组

File _file[OPEN_MAX] = 
{
        {1}, /* stdin */
        {1}, /* stdout */
        {1},  /* stderr */
        {1},  /* kbdin */
        {1}  /* vgaout */
};
其中传递进来的参数 fd 表示句柄的数值,这个值一是从0开始:

                devp->dev = dev;
                _file[fd].data = (void *)devp;
这样以后根据句柄值就可以找到需要的设备。


  可以跟踪下 printf 函数的调用流程:

printf (const char *fmt, ...)                                                                  lib/libc/printf.c
vfprintf (FILE *fp, const char *fmt, va_list ap)                                               lib/libc/vfprintf.c
fputs(const char *p, FILE *fp)                                                                 lib/libc/fputs.c
write (int fd, const void *buf, size_t n)                                                      lib/libc/write.c
其中在write里面调用函数 _file[fd]).fs->write 。这里的write指针指向函数 term_write。
term_write (int fd, const void *buf, size_t nchar)                                             pmon/fs/termio.c 
stdout定义如下:

#define stdout  (vga_available?(&_iob[4]):(&_iob[1]))


int term_write (int fd, const void *buf, size_t nchar)
{
        DevEntry       *p;
        struct TermDev *devp;
        char *buf2 = (char *)buf;
        int i, n;
        int dsel;
        int count;

        devp = (struct TermDev *)_file[fd].data;
        p = &DevTable[devp->dev];
        n = nchar;
        dsel=devp->dev;

        do
        {
                p = &DevTable[dsel];
                buf2 = (char *)buf;
                n = nchar;

                while (n > 0) {
                        /* << LOCK >> */
                        while(!tgt_smplock());

                        i = Qspace (p->txq);
                        while (i > 2 && n > 0) {
                                if ((p->t.c_oflag & ONLCR) && *buf2 == '\n') {
                                        Qput(p->txq, '\r');
                                        i--;
                                }
                                Qput(p->txq, *buf2++);
                                n--;
                                i--;
                        }
                        tgt_smpunlock();
                        /* << UNLOCK >> */

                        while (Qused(p->txq)) {
                                scandevs();
                        }
                }
                dsel=DevTable[dsel+1].handler?dsel+1:0;
        }while(dsel!=devp->dev && output_to_both);

        return (nchar);
}
上面函数中首先调用 Qput把需要发送的消息放到消息队列中。

然后再调用 scandevs 扫描 DevTable 数组里的每一个设备的队列,把队列中的消息发送出去。 通过函数指针handler 这里handler指向函数 ns16550。


如何扩展串口数目?

pmon只有一个串口可以输出,如何让两个串口同时操作。

首先说下串口的初始化;./Targets/LS1B/ls1b/start.S。串口的初始化是从这里开始,pmon以后都接着使用这个串口,这里如果不初始化后面不能用。

        la      v0, COM1_BASE_ADDR
        li      v1, FIFO_ENABLE |FIFO_RCV_RST|FIFO_XMT_RST|FIFO_TRIGGER_4
        sb      v1, NSREG(NS16550_FIFO)(v0)
        li      v1, CFCR_DLAB
        sb      v1, NSREG(NS16550_CFCR)(v0)

        li      a1,2*16*CONS_BAUD
        divu    v1,v1,a1
        move    t0,v1

        sb      v1, NSREG(NS16550_DATA)(v0)
        srl     v1, 8
        sb      v1, NSREG(NS16550_IER)(v0)
        li      v1, CFCR_8BITS
        sb      v1, NSREG(NS16550_CFCR)(v0)
        li      v1, MCR_DTR|MCR_RTS
        sb      v1, NSREG(NS16550_MCR)(v0)
        li      v1, 0x0
        sb      v1, NSREG(NS16550_IER)(v0)
        #disable all interrupt
        li      v1, 0x0
        sb      v1, NSREG(NS16550_IER)(v0)
在这里也应当加入串口2初始化的代码,具体就仿照串口1就行了。

剩下的就是修改c文件

首先在ConfigTable 里面加入第二个串口的配置:

ConfigEntry     ConfigTable[] =
{
#ifdef CONFIG_EJTAG_SERIAL
         { (char *)COM1_BASE_ADDR, 0, ejtag_serial, 256, CONS_BAUD, NS16550HZ},
#else
         { (char *)COM1_BASE_ADDR, 0, ns16550, 256, CONS_BAUD, NS16550HZ},
#endif
        { (char *)COM2_BASE_ADDR, 0, ns16550, 256, CONS_BAUD, NS16550HZ},
 #if  NMOD_VGACON >0 && NMOD_FRAMEBUFFER >0
        { (char *)1, 0, fbterm, 256, CONS_BAUD, NS16550HZ },
#endif
        { 0 }
};

与第一个串口除地址不同外其它的值都相同。

这样在函数 devinit 里就会自动扫描这个数组并初始化:

在   init_fs() 里面就会调用 term_open 函数打开第6个设备的函数调用

最后 在 函数里  scandevs 里就会处理用户的输入,并打印输出。



参考文章:

http://blog.chinaunix.net/uid-14214482-id-330781.html



你可能感兴趣的:(pmon 的 printf 初始化及调用流分析)