RTEMS 的 Console 的基本架构分析

Console 的基本架构

首先配置需要启动 CONSOLE 驱动,另外还需要文件系统的支持,可以配置 devfs 或者 imfs 都可以。主要是提供一个 /dev/console 的节点注册,访问等支持。

#defineCONFIGURE_APPLICATION_NEEDS_CONSOLE_DRIVER

#defineCONFIGURE_USE_DEVFS_AS_BASE_FILESYSTEM

 

其中CONFIGURE_APPLICATION_NEEDS_CONSOLE_DRIVER 的配置,那么confdefs.h 中的Device_drivers 数组加入了CONSOLE_DRIVER_TABLE_ENTRY

而这个 entry 的定义是驱动所需要的所有函数,具体如下

#define CONSOLE_DRIVER_TABLE_ENTRY\

  {console_initialize, console_open, console_close, \

console_read,console_write, console_control }

 

作为串口驱动,我们可以完全自己实现以上的几个函数,例子可以看\arm\csb336\console ,(这个例子作为后面用,可以做个了解。),rtems本身也抽象了一个公用的串口驱动,用户只需要实现具体串口相关的函数,libbsp\shared\ 目录下,consoleXXX.c 函数,rtems的 arm bsp 大多使用了第二种方案,例如arm\stm32f4\console

 

 

下面以arm\stm32f4\console 为例子说说 poll (死循环查询)的console的实现方法。

 

从libbsp\shared\ 的console 文件中可以看出,console 的read write 方法其实就是调用termios 系统,所以说,说白了其实读的代码就是 termios。

初始化的流程是:

boot_card --rtems_initialize_device_drivers -- _IO_Initialize_all_drivers -- rtems_io_initialize

 

在rtems_io_initialize 函数中,根据 _IO_Driver_address_table,顺序的调用每个成员的 initialization_entry 函数。而_IO_Driver_address_table 作为一个系统的 _IO_Manager_initialization中初始化的,其实最后指向的就是 Device_drivers ,也就是我们可以配置的那个,这里的串口就是指CONSOLE_DRIVER_TABLE_ENTRY 所以 initialization_entry其实指向的就是 console_initialize

 

console_initialize函数做了3件事,首先 rtems_termios_initialize初始化 termios,只是初始化信号量。接着console_initialize_hardware 初始化硬件,最后 rtems_io_register_name注册设备,并且创建 /dev/console 节点,都完成之后 console 设备就已经准备好了。

 

对于devfs的系统

Write à devFS_writeà rtems_deviceio_write à rtems_io_write

最后根据已经注册的设备的write_entry 找到写句柄,这里是console_write à rtems_termios_write 最后绕到 termios 的写方法上面来了。

 

写方法,只有一个参数,这个参数是在在rtems_termios_open 中连接到节点的 data1 上面的 args->iop->data1= tty; 所以写方法的时候就可以读出这个节点, tty 结构可以理解为一个对象,包含了这个tty设备的所有信息。

rtems_status_code

rtems_termios_write (void *arg)

{

 rtems_libio_rw_args_t *args = arg;

 struct rtems_termios_tty *tty = args->iop->data1;

 

写方法首先得到信号量,确保多线程访问的时候只有一个线程获取信号量。

  sc= rtems_semaphore_obtain (tty->osem, RTEMS_WAIT, RTEMS_NO_TIMEOUT);

  if(sc != RTEMS_SUCCESSFUL)

return sc;

 

根据标志位判断,输出流是否需要处理特殊符号,例如 \r \n 等符号,对于console 来说这是需要的,但对于其他串口设备来说,可能他们有自己的处理函数,所以设置标志位的时候需要注意。Oproc 处理完后还是调用rtems_termios_puts 的,所以两个处理的流程差不多。

if (tty->termios.c_oflag & OPOST) {

   uint32_t   count = args->count;

   char      *buffer =args->buffer;

   while (count--)

     oproc (*buffer++, tty);

   args->bytes_moved = args->count;

  }else {

   rtems_termios_puts (args->buffer, args->count, tty);

   args->bytes_moved = args->count;

  }

 

在oproc 中处理一些特殊控制符,例如收到 \n 是否需要先发送一个 \r ,\b 回退一个字符等等,处理完之后就输出

 

rtems_termios_puts 函数中根据device 设置的是否POLLED 模式,也就是轮训模式,如果是的话,则直接调用串口的写函数 device.write 就是串口提供的自身的写方法,很简单,死循环送出len 个数据就行了。

if (tty->device.outputUsesInterrupts ==TERMIOS_POLLED) {

   (*tty->device.write)(tty->minor, buf, len);

   return;

  }

 

如果是 POLLED 模式,则以上就足够了,但如果是 interrupt 模式,就比较复杂了。

对 rawOutBuf 进行操作,这是一个环形队列

 newHead = tty->rawOutBuf.Head;

 while (len) {

         放入一个数据后新的头

newHead =(newHead + 1) % tty->rawOutBuf.Size;

关中断,因为 环形缓冲在中断中被使用了,所以先上锁

   rtems_termios_interrupt_lock_acquire (tty, level);

         头+1 == 尾表示队列已经满了,则重新开中断,等待信号量,这个信号量是在中断里面释放的,中断将数据发出去了就发这个信号量。直到等到信号量为止

   while (newHead == tty->rawOutBuf.Tail) {

     tty->rawOutBufState = rob_wait;

     rtems_termios_interrupt_lock_release (tty, level);

     sc = rtems_semaphore_obtain(

       tty->rawOutBuf.Semaphore, RTEMS_WAIT, RTEMS_NO_TIMEOUT);

     if (sc != RTEMS_SUCCESSFUL)

       rtems_fatal_error_occurred (sc);

     rtems_termios_interrupt_lock_acquire (tty, level);

}

队列已经有空位了,则放入数据

   tty->rawOutBuf.theBuf[tty->rawOutBuf.Head] = *buf++;

tty->rawOutBuf.Head= newHead;

如果之前的缓冲状态是 idle,则判断流控,如果没流控信号,则直接通过 device.write 方法写到串口上面,然后置状态为 busy,注意,这个写方法和之前的POLL模式的写方法是不同的

   if (tty->rawOutBufState == rob_idle) {

     /* check, whether XOFF has been received */

     if (!(tty->flow_ctrl & FL_ORCVXOF)) {

       (*tty->device.write)(

         tty->minor, &tty->rawOutBuf.theBuf[tty->rawOutBuf.Tail],1);

     } else {

       /* remember that output has been stopped due to flow ctrl*/

       tty->flow_ctrl |= FL_OSTOP;

     }

     tty->rawOutBufState = rob_busy;

}

重新打开中断

   rtems_termios_interrupt_lock_release (tty, level);

   len--;

  }

循环直到所有数据都发出去为止。

 

一个中断串口的例子在\libbsp\arm\csb336\console\uart.c

 

中断模式的写方法,只存放数据的指针,或者写入自己的环形队列,等等方法实现都行,具体看具体的IC,然后开中断,则串口模块就自己发送数据了,发送完之后产生中断

static ssize_t imx_uart_intr_write(int minor,const char *buf, size_t len)

{

   if (len > 0) {

       imx_uart_data[minor].buf = buf;

       imx_uart_data[minor].len = len;

       imx_uart_data[minor].idx = 0;

 

       imx_uart_data[minor].regs->cr1 |= MC9328MXL_UART_CR1_TXMPTYEN;

    }

 

    return 1;

}

 

中断的处理函数,

static void imx_uart_tx_isr(void * param)

{

   imx_uart_data_t *uart_data = param;

   int len;

   int minor = uart_data->minor;

 

判断写缓冲里面还有没有数据,如果有则继续发送

   if (uart_data->idx < uart_data->len) {

       while ( (uart_data->regs->sr1 & MC9328MXL_UART_SR1_TRDY)&&

                (uart_data->idx <uart_data->len)) {

           uart_data->regs->txd = uart_data->buf[uart_data->idx];

           uart_data->idx++;

       }

} else {

没有数据,所有数据都发送完毕了,

       len = uart_data->len;

       uart_data->len = 0;

       imx_uart_data[minor].regs->cr1 &= ~MC9328MXL_UART_CR1_TXMPTYEN;

                   这里需要重新装填数据

       rtems_termios_dequeue_characters(uart_data->tty,len);

    }

}

 

rtems_termios_puts 函数不会等所有数据都发送完毕,而是等所有的数据都放入缓冲,就直接返回的了。然后由设备的中断函数不断的调用rtems_termios_dequeue_characters ,从tty缓冲中取出数据然后发送,直到最后自动完成。

 

对于写操作rtems_termios_read 则稍微有点不同,

首先准备数据,数据是存放在 cbuf 当中的,根据串口的模式是 poll 还是中断分别调用 fillBufferPoll 和 fillBufferQueue,对于 poll 操作没啥好说的,就是死等。直到遇到回车换行为止。对于中断,应该使用 queue 操作

if (tty->cindex == tty->ccount) {

   tty->cindex = tty->ccount = 0;

   tty->read_start_column = tty->column;

   if (tty->device.pollRead != NULL &&

       tty->device.outputUsesInterrupts == TERMIOS_POLLED)

     sc = fillBufferPoll (tty);

   else

     sc = fillBufferQueue (tty);

 

   if (sc != RTEMS_SUCCESSFUL)

     tty->cindex = tty->ccount = 0;

  }

 

以上操作完成后直接复制数据到输出buf中,返回读到了多少个数据,所以重点应该是上面的函数

while (count && (tty->cindex< tty->ccount)) {

   *buffer++ = tty->cbuf[tty->cindex++];

   count--;

  }

 args->bytes_moved = args->count - count;

 

 

对于 fillBufferPoll

首先判断是否标准的tty模式,因为用户可能需要自己来处理串口的消息,并不希望采用

if (tty->termios.c_lflag & ICANON) {

for (;;) {

  从串口中读取一个字符,这个应该是非堵塞的,只返回当前的一个字符,没有则返回-1

     n = (*tty->device.pollRead)(tty->minor);

     if (n < 0) {

       没有则等待一个tick

       rtems_task_wake_after (1);

     } else {

       如果读到的是普通字符,则返回0,继续读,因为是标准的,所以必须是读到一行结束或者文件结束符为止的,至于其他控制字符也在 siproc 中处理,例如读到回车,则读取函数就结束了。或者读取到 cbuf慢了,也同样结束。

       if  (siproc (n, tty))

         break;

     }

    }

  }

else {

         这个是非标准模式,则需要判断两个东西 VMIN 和 VTIME,这2个可能设置其中一个,也可能同时设置或者同时不设置,VMIN 表示至少读多少个字符,VTIME 表示超时

 

   rtems_interval then, now;

         先读出当前的时间

   then = rtems_clock_get_ticks_since_boot();

   for (;;) {

     n = (*tty->device.pollRead)(tty->minor);

     读取一个字符,如果错误,表示没有数据

     if (n < 0) {

       没有数据就判断有没有设定最少读取一个数据,如果有,则应该继续等,一直等到超时为止

       if (tty->termios.c_cc[VMIN]) {

         if (tty->termios.c_cc[VTIME] && tty->ccount) {

           now = rtems_clock_get_ticks_since_boot();

           if ((now - then) > tty->vtimeTicks) {

              break;

           }

         }

       } else {

         如果没有设定最少数据,则判断是否有设置超时,如果都没有设置,则直接错误退出了。如果有超时,则判断当前时间和开始的时间有没有超过预设的时间,有则超时

         if (!tty->termios.c_cc[VTIME])

           break;

         now = rtems_clock_get_ticks_since_boot();

         if ((now - then) > tty->vtimeTicks) {

           break;

         }

       }

       rtems_task_wake_after (1);

     } else {

       如果有数据则处理,超过最少数据则立刻返回

       siproc (n, tty);

       if (tty->ccount >= tty->termios.c_cc[VMIN])

         break;

       if (tty->termios.c_cc[VMIN] && tty->termios.c_cc[VTIME])

         then = rtems_clock_get_ticks_since_boot();

     }

    }

  }

在fillBufferQueue 核心的思想就是:在一个给定的时间内(timeout)从串口接收缓冲rawInBuf 中拿数据,存放在cbuf 中

循环等待

  while( wait ) {

   /*

    * Process characters read from raw queue

    */

         输入队列中海油数据 并且 cbuf 不能超过最大容量

   while ((tty->rawInBuf.Head != tty->rawInBuf.Tail) &&

                       (tty->ccount <(CBUFSIZE-1))) {

     unsigned char c;

     unsigned int newHead;

        

           从输入缓冲中读出一个字符

     newHead = (tty->rawInBuf.Head + 1) % tty->rawInBuf.Size;

     c = tty->rawInBuf.theBuf[newHead];

     tty->rawInBuf.Head = newHead;

         如果缓冲的数据取得差不多,超过了 low water level 则重新发 XON 信号,这是流控的处理

     if(((tty->rawInBuf.Tail-newHead+tty->rawInBuf.Size)

         % tty->rawInBuf.Size)

        < tty->lowwater) {

       tty->flow_ctrl &= ~FL_IREQXOF;

       /* if tx stopped and XON should be sent... */

       if (((tty->flow_ctrl & (FL_MDXON | FL_ISNTXOF))

            ==                (FL_MDXON |FL_ISNTXOF))

           && ((tty->rawOutBufState == rob_idle)

         || (tty->flow_ctrl & FL_OSTOP))) {

         /* XON should be sent now... */

         (*tty->device.write)(

           tty->minor, (void *)&(tty->termios.c_cc[VSTART]), 1);

       } else if (tty->flow_ctrl & FL_MDRTS) {

         tty->flow_ctrl &= ~FL_IRTSOFF;

         /* activate RTS line */

         if (tty->device.startRemoteTx != NULL) {

           tty->device.startRemoteTx(tty->minor);

         }

       }

     }

          将接收到的数据送进去siproc 中处理,例如处理一些回车,换行,退格等等的特殊符号

     /* continue processing new character */

     if (tty->termios.c_lflag & ICANON) {

       if (siproc (c, tty))

         wait = 0;

     } else {

       siproc (c, tty);

       if (tty->ccount >= tty->termios.c_cc[VMIN])

         wait = 0;

     }

     timeout = tty->rawInBufSemaphoreTimeout;

    }

 

    如果

   if ( wait ) {

     sc = rtems_semaphore_obtain(

       tty->rawInBuf.Semaphore, tty->rawInBufSemaphoreOptions, timeout);

     if (sc != RTEMS_SUCCESSFUL)

       break;

    }

  }

 

而具体的驱动中,就没有了 pollread 方法的,应该在中断中调用rtems_termios_enqueue_raw_characters 来确定收到的数据。

 

static void imx_uart_rx_isr(void * param)

{

   imx_uart_data_t *uart_data = param;

   char buf[32];

   int i=0;

 

   while (uart_data->regs->sr2 & MC9328MXL_UART_SR2_RDR) {

       buf[i] = uart_data->regs->rxd & 0xff;

       i++;

    }

 

    rtems_termios_enqueue_raw_characters(uart_data->tty,buf, i);

}

 

大概的脉络就分析完毕了,我大概读了2-3天的源代码,结合百度,算是理解清晰了。感觉代码写的真好,将我之前的所有疑问基本都消除了。RTEMS真是个宝藏。

 

2014-3-13

Etual

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值