前言
既然分析完了V1的串口框架,那就也得顺便把V2的框架也看了,之前RTT论坛也讨论过两版框架的效率,但对比代码发现,其实差异还挺大,直接对比修改项的方式并不靠谱。
实际上,对驱动层面来说,适配V1版串口的驱动程序,并不能直接用在V2版框架上,同样的,V2版框架的驱动程序也不能直接用在V1上,因此还是得重头分析。
代码解析
V1框架的代码主要放置于/components/drivers/serial/serial_v2.c中,因此学习也是围绕该文件而展开的。
串口注册入口
还是一样的套路,通过在里面查找struct rt_device_ops的方式查找代码,查到后再反查对应注册入口:
#ifdef RT_USING_DEVICE_OPS const static struct rt_device_ops serial_ops = { rt_serial_init, rt_serial_open, rt_serial_close, rt_serial_read, rt_serial_write, rt_serial_control }; #endif rt_err_t rt_hw_serial_register(struct rt_serial_device *serial, const char *name, rt_uint32_t flag, void *data) { rt_err_t ret; struct rt_device *device; RT_ASSERT(serial != RT_NULL); device = &(serial->parent); device->type = RT_Device_Class_Char; device->rx_indicate = RT_NULL; device->tx_complete = RT_NULL; #ifdef RT_USING_DEVICE_OPS device->ops = &serial_ops; #else device->init = rt_serial_init; device->open = rt_serial_open; device->close = rt_serial_close; device->read = RT_NULL; device->write = RT_NULL; device->control = rt_serial_control; #endif device->user_data = data; /* register a character device */ ret = rt_device_register(device, name, flag); #ifdef RT_USING_POSIX_STDIO /* set fops */ device->fops = &_serial_fops; #endif return ret; }
通过对比这部分的代码和V1版的差别,会发现,如果不考虑RT_SMART包裹的部分,实际上两个版本差异很小。一是未定义 RT_USING_DEVICE_OPS 时,没有指定读写函数。二是,去掉了自旋锁操作。这些差异,只能在后续分析代码时逐步解答了。
串口初始化入口
static rt_err_t rt_serial_init(struct rt_device *dev) { rt_err_t result = RT_EOK; struct rt_serial_device *serial; RT_ASSERT(dev != RT_NULL); serial = (struct rt_serial_device *)dev; RT_ASSERT(serial->ops->transmit != RT_NULL); /* initialize rx/tx */ serial->serial_rx = RT_NULL; serial->serial_tx = RT_NULL; /* apply configuration */ if (serial->ops->configure) result = serial->ops->configure(serial, &serial->config); return result; }
从初始化入口上看,对比V1,逻辑上并没有改动,但是去掉了V1莫名其妙加入的notify接口。
串口打开入口
static rt_err_t rt_serial_tx_enable(struct rt_device *dev, rt_uint16_t tx_oflag) { struct rt_serial_device *serial; struct rt_serial_tx_fifo *tx_fifo = RT_NULL; RT_ASSERT(dev != RT_NULL); serial = (struct rt_serial_device *)dev; if (serial->config.tx_bufsz == 0) { /* Cannot use RT_SERIAL_TX_NON_BLOCKING when tx_bufsz is 0 */ if (tx_oflag == RT_SERIAL_TX_NON_BLOCKING) { LOG_E("(%s) serial device with misconfigure: tx_bufsz = 0", dev->parent.name); return -RT_EINVAL; } #ifndef RT_USING_DEVICE_OPS dev->write = _serial_poll_tx; #endif dev->open_flag |= RT_SERIAL_TX_BLOCKING; return RT_EOK; } /* Limits the minimum value of tx_bufsz */ if (serial->config.tx_bufsz < RT_SERIAL_TX_MINBUFSZ) serial->config.tx_bufsz = RT_SERIAL_TX_MINBUFSZ; if (tx_oflag == RT_SERIAL_TX_BLOCKING) { /* When using RT_SERIAL_TX_BLOCKING, it is necessary to determine * whether serial device needs to use buffer */ rt_err_t optmode; /* The operating mode used by serial device */ /* Call the Control() API to get the operating mode */ optmode = serial->ops->control(serial, RT_DEVICE_CHECK_OPTMODE, (void *)RT_DEVICE_FLAG_TX_BLOCKING); if (optmode == RT_SERIAL_TX_BLOCKING_BUFFER) { /* If use RT_SERIAL_TX_BLOCKING_BUFFER, the ringbuffer is initialized */ tx_fifo = (struct rt_serial_tx_fifo *) rt_malloc (sizeof(struct rt_serial_tx_fifo) + serial->config.tx_bufsz); RT_ASSERT(tx_fifo != RT_NULL); rt_ringbuffer_init(&(tx_fifo->rb), tx_fifo->buffer, serial->config.tx_bufsz); serial->serial_tx = tx_fifo; #ifndef RT_USING_DEVICE_OPS dev->write = _serial_fifo_tx_blocking_buf; #endif } else { /* If not use RT_SERIAL_TX_BLOCKING_BUFFER, * the control() API is called to configure the serial device */ tx_fifo = (struct rt_serial_tx_fifo*) rt_malloc (sizeof(struct rt_serial_tx_fifo)); RT_ASSERT(tx_fifo != RT_NULL); /* Init rb.buffer_ptr to RT_NULL, in rt_serial_write() need check it * otherwise buffer_ptr maybe a random value, as rt_malloc not init memory */ tx_fifo->rb.buffer_ptr = RT_NULL; serial->serial_tx = tx_fifo; #ifndef RT_USING_DEVICE_OPS dev->write = _serial_fifo_tx_blocking_nbuf; #endif /* Call the control() API to configure the serial device by RT_SERIAL_TX_BLOCKING*/ serial->ops->control(serial, RT_DEVICE_CTRL_CONFIG, (void *)RT_SERIAL_TX_BLOCKING); rt_memset(&tx_fifo->rb, RT_NULL, sizeof(tx_fifo->rb)); } tx_fifo->activated = RT_FALSE; tx_fifo->put_size = 0; rt_completion_init(&(tx_fifo->tx_cpt)); dev->open_flag |= RT_SERIAL_TX_BLOCKING; return RT_EOK; } /* When using RT_SERIAL_TX_NON_BLOCKING, ringbuffer needs to be initialized, * and initialize the tx_fifo->activated value is RT_FALSE. */ tx_fifo = (struct rt_serial_tx_fifo *) rt_malloc (sizeof(struct rt_serial_tx_fifo) + serial->config.tx_bufsz); RT_ASSERT(tx_fifo != RT_NULL); tx_fifo->activated = RT_FALSE; tx_fifo->put_size = 0; rt_ringbuffer_init(&(tx_fifo->rb), tx_fifo->buffer, serial->config.tx_bufsz); serial->serial_tx = tx_fifo; #ifndef RT_USING_DEVICE_OPS dev->write = _serial_fifo_tx_nonblocking; #endif dev->open_flag |= RT_SERIAL_TX_NON_BLOCKING; /* Call the control() API to configure the serial device by RT_SERIAL_TX_NON_BLOCKING*/ serial->ops->control(serial, RT_DEVICE_CTRL_CONFIG, (void *)RT_SERIAL_TX_NON_BLOCKING); return RT_EOK; } static rt_err_t rt_serial_rx_enable(struct rt_device *dev, rt_uint16_t rx_oflag) { struct rt_serial_device *serial; struct rt_serial_rx_fifo *rx_fifo = RT_NULL; RT_ASSERT(dev != RT_NULL); serial = (struct rt_serial_device *)dev; if (serial->config.rx_bufsz == 0) { /* Cannot use RT_SERIAL_RX_NON_BLOCKING when rx_bufsz is 0 */ if (rx_oflag == RT_SERIAL_RX_NON_BLOCKING) { LOG_E("(%s) serial device with misconfigure: rx_bufsz = 0", dev->parent.name); return -RT_EINVAL; } #ifndef RT_USING_DEVICE_OPS dev->read = _serial_poll_rx; #endif dev->open_flag |= RT_SERIAL_RX_BLOCKING; return RT_EOK; } /* Limits the minimum value of rx_bufsz */ if (serial->config.rx_bufsz < RT_SERIAL_RX_MINBUFSZ) serial->config.rx_bufsz = RT_SERIAL_RX_MINBUFSZ; rx_fifo = (struct rt_serial_rx_fifo *) rt_malloc (sizeof(struct rt_serial_rx_fifo) + serial->config.rx_bufsz); RT_ASSERT(rx_fifo != RT_NULL); rt_ringbuffer_init(&(rx_fifo->rb), rx_fifo->buffer, serial->config.rx_bufsz); serial->serial_rx = rx_fifo; #ifndef RT_USING_DEVICE_OPS dev->read = _serial_fifo_rx; #endif if (rx_oflag == RT_SERIAL_RX_NON_BLOCKING) { dev->open_flag |= RT_SERIAL_RX_NON_BLOCKING; /* Call the control() API to configure the serial device by RT_SERIAL_RX_NON_BLOCKING*/ serial->ops->control(serial, RT_DEVICE_CTRL_CONFIG, (void *) RT_SERIAL_RX_NON_BLOCKING); return RT_EOK; } /* When using RT_SERIAL_RX_BLOCKING, rt_completion_init() and rx_cpt_index are initialized */ rx_fifo->rx_cpt_index = 0; rt_completion_init(&(rx_fifo->rx_cpt)); dev->open_flag |= RT_SERIAL_RX_BLOCKING; /* Call the control() API to configure the serial device by RT_SERIAL_RX_BLOCKING*/ serial->ops->control(serial, RT_DEVICE_CTRL_CONFIG, (void *) RT_SERIAL_RX_BLOCKING); return RT_EOK; } static rt_err_t rt_serial_open(struct rt_device *dev, rt_uint16_t oflag) { struct rt_serial_device *serial; RT_ASSERT(dev != RT_NULL); serial = (struct rt_serial_device *)dev; /* Check that the device has been turned on */ if ((dev->open_flag) & (15 << 12)) { LOG_D("(%s) serial device has already been opened, it will run in its original configuration", dev->parent.name); return RT_EOK; } LOG_D("open serial device: 0x%08x with open flag: 0x%04x", dev, oflag); /* By default, the receive mode of a serial devide is RT_SERIAL_RX_NON_BLOCKING */ if ((oflag & RT_SERIAL_RX_BLOCKING) == RT_SERIAL_RX_BLOCKING) dev->open_flag |= RT_SERIAL_RX_BLOCKING; else dev->open_flag |= RT_SERIAL_RX_NON_BLOCKING; /* By default, the transmit mode of a serial devide is RT_SERIAL_TX_BLOCKING */ if ((oflag & RT_SERIAL_TX_NON_BLOCKING) == RT_SERIAL_TX_NON_BLOCKING) dev->open_flag |= RT_SERIAL_TX_NON_BLOCKING; else dev->open_flag |= RT_SERIAL_TX_BLOCKING; /* set steam flag */ if ((oflag & RT_DEVICE_FLAG_STREAM) || (dev->open_flag & RT_DEVICE_FLAG_STREAM)) dev->open_flag |= RT_DEVICE_FLAG_STREAM; /* initialize the Rx structure according to open flag */ if (serial->serial_rx == RT_NULL) rt_serial_rx_enable(dev, dev->open_flag & (RT_SERIAL_RX_BLOCKING | RT_SERIAL_RX_NON_BLOCKING)); /* initialize the Tx structure according to open flag */ if (serial->serial_tx == RT_NULL) rt_serial_tx_enable(dev, dev->open_flag & (RT_SERIAL_TX_BLOCKING | RT_SERIAL_TX_NON_BLOCKING)); return RT_EOK; }
相比较于V1版本的,修改点有点多:
1. 被我吐槽的违背了一页就能看完整个函数实现原则的问题已经在一定程度上被修复,tx的打开逻辑被封装到函数rt_serial_tx_enable内实现,RX的打开逻辑也同样被封装到函数rt_serial_rx_enable内实现。
2. V1版的中断,DMA,轮询的操作变成了BLOCKING和NON_BLOCKING方式配置了
3. queue队列改为ringbuffer实现了,因为ringbuffer独有的特点(后续专门分析ringbuffer),刚好可以在不需要加自旋锁的情况下满足读写要求
4. 在没有定义RT_USING_DEVICE_OPS时,write接口被指向了函数_serial_fifo_tx_blocking_nbuf、_serial_poll_tx和_serial_fifo_tx_nonblocking中的一个,而read接口被指向了_serial_poll_rx 或 _serial_fifo_rx,至于为何要这么做,因为这一部分是分析打开接口的,不便展开,就留在后面分析读写接口时再去具体分析。
串口读操作
在前面的分析,我们其实已经知道了读有三种接口了,下面就需要对四种接口针对性分析。
rt_serial_read
static rt_ssize_t rt_serial_read(struct rt_device *dev, rt_off_t pos, void *buffer, rt_size_t size) { struct rt_serial_device *serial; RT_ASSERT(dev != RT_NULL); if (size == 0) return 0; serial = (struct rt_serial_device *)dev; if (serial->config.rx_bufsz) { return _serial_fifo_rx(dev, pos, buffer, size); } return _serial_poll_rx(dev, pos, buffer, size); }
看到这,再结合open函数里面那花哨的操作,只能说,其实最初的设计应该就是open函数里面的做法,至于为什么重新定义一个read函数,本质上仅仅是因为struct rt_device_ops仅有一个read入口,因此需要对应的封装一层罢了。
_serial_poll_rx
rt_ssize_t _serial_poll_rx(struct rt_device *dev, rt_off_t pos, void *buffer, rt_size_t size) { struct rt_serial_device *serial; rt_size_t getc_size; int getc_element; /* Gets one byte of data received */ rt_uint8_t *getc_buffer; /* Pointer to the receive data buffer */ RT_ASSERT(dev != RT_NULL); serial = (struct rt_serial_device *)dev; RT_ASSERT(serial != RT_NULL); getc_buffer = (rt_uint8_t *)buffer; getc_size = size; while(size) { getc_element = serial->ops->getc(serial); if (getc_element == -1) break; *getc_buffer = getc_element; ++ getc_buffer; -- size; if (serial->parent.open_flag & RT_DEVICE_FLAG_STREAM) { /* If open_flag satisfies RT_DEVICE_FLAG_STREAM * and the received character is '\n', exit the loop directly */ if (getc_element == '\n') break; } } return getc_size - size; }
对比V1的写法,会发现,其实这仅仅相当于重构了poll函数,真正的实现逻辑和原先还是一模一样。
_serial_fifo_rx
static rt_ssize_t _serial_fifo_rx(struct rt_device *dev, rt_off_t pos, void *buffer, rt_size_t size) { struct rt_serial_device *serial; struct rt_serial_rx_fifo *rx_fifo; rt_base_t level; rt_size_t recv_len; /* The length of data from the ringbuffer */ RT_ASSERT(dev != RT_NULL); if (size == 0) return 0; serial = (struct rt_serial_device *)dev; RT_ASSERT((serial != RT_NULL) && (buffer != RT_NULL)); rx_fifo = (struct rt_serial_rx_fifo *) serial->serial_rx; if (dev->open_flag & RT_SERIAL_RX_BLOCKING) { if (size > serial->config.rx_bufsz) { LOG_W("(%s) serial device received data:[%d] larger than " "rx_bufsz:[%d], please increase the BSP_UARTx_RX_BUFSIZE option", dev->parent.name, size, serial->config.rx_bufsz); return 0; } /* Get the length of the data from the ringbuffer */ recv_len = rt_ringbuffer_data_len(&(rx_fifo->rb)); if (recv_len < size) { /* When recv_len is less than size, rx_cpt_index is updated to the size * and rt_current_thread is suspend until rx_cpt_index is equal to 0 */ rx_fifo->rx_cpt_index = size; rt_completion_wait(&(rx_fifo->rx_cpt), RT_WAITING_FOREVER); } } /* This part of the code is open_flag as RT_SERIAL_RX_NON_BLOCKING */ level = rt_hw_interrupt_disable(); /* When open_flag is RT_SERIAL_RX_NON_BLOCKING, * the data is retrieved directly from the ringbuffer and returned */ recv_len = rt_ringbuffer_get(&(rx_fifo->rb), buffer, size); rt_hw_interrupt_enable(level); return recv_len; }
由于引入了ringbuffer机制,读操作就变得非常简单,仅仅需要判断现有buffer数据是否足够,若不足,则用完成量的方式等待串口接收到足够多的数据再往下执行。若够了,则直接执行读数据的操作。至于为何这里需要开关全局中断,其实我是比较迷糊的,照理来说,ringbuffer机制已经不需要再去进行加锁操作了才对(除非存在多个线程同时读取串口的场景),而这个疑惑,也只有等后面分析ringbuffer时再去解开了。
串口写操作
同样的,在V2版的串口中,串口写操作也被分成了4个接口。而经过了读操作的分析,其实也大致可以猜到实际上是3个接口,具体代码如下:
rt_serial_write
static rt_ssize_t rt_serial_write(struct rt_device *dev, rt_off_t pos, const void *buffer, rt_size_t size) { struct rt_serial_device *serial; struct rt_serial_tx_fifo *tx_fifo; RT_ASSERT(dev != RT_NULL); if (size == 0) return 0; serial = (struct rt_serial_device *)dev; RT_ASSERT((serial != RT_NULL) && (buffer != RT_NULL)); tx_fifo = (struct rt_serial_tx_fifo *) serial->serial_tx; if (serial->config.tx_bufsz == 0) { return _serial_poll_tx(dev, pos, buffer, size); } if (dev->open_flag & RT_SERIAL_TX_BLOCKING) { if ((tx_fifo->rb.buffer_ptr) == RT_NULL) { return _serial_fifo_tx_blocking_nbuf(dev, pos, buffer, size); } return _serial_fifo_tx_blocking_buf(dev, pos, buffer, size); } return _serial_fifo_tx_nonblocking(dev, pos, buffer, size); }
结合open函数,其实可以很明显的看出,就是因为结构体需要,封装出来的函数。
_serial_poll_tx
rt_ssize_t _serial_poll_tx(struct rt_device *dev, rt_off_t pos, const void *buffer, rt_size_t size) { struct rt_serial_device *serial; rt_size_t putc_size; rt_uint8_t *putc_buffer; /* Pointer to the transmit data buffer */ RT_ASSERT(dev != RT_NULL); serial = (struct rt_serial_device *)dev; RT_ASSERT(serial != RT_NULL); putc_buffer = (rt_uint8_t *)buffer; putc_size = size; while (size) { if (serial->parent.open_flag & RT_DEVICE_FLAG_STREAM) { /* If open_flag satisfies RT_DEVICE_FLAG_STREAM and the received character is '\n', * inserts '\r' character before '\n' character for the effect of carriage return newline */ if (*putc_buffer == '\n') serial->ops->putc(serial, '\r'); } serial->ops->putc(serial, *putc_buffer); ++ putc_buffer; -- size; } return putc_size - size; }
由于是轮询方式的写,本质上就是直接在当前任务上一个字符一个字符的写下去,因此操作和V1的poll没啥区别,仅仅是重构了一下。
_serial_fifo_tx_blocking_nbuf
static rt_ssize_t _serial_fifo_tx_blocking_nbuf(struct rt_device *dev, rt_off_t pos, const void *buffer, rt_size_t size) { struct rt_serial_device *serial; struct rt_serial_tx_fifo *tx_fifo = RT_NULL; rt_ssize_t rst; RT_ASSERT(dev != RT_NULL); if (size == 0) return 0; serial = (struct rt_serial_device *)dev; RT_ASSERT((serial != RT_NULL) && (buffer != RT_NULL)); tx_fifo = (struct rt_serial_tx_fifo *) serial->serial_tx; RT_ASSERT(tx_fifo != RT_NULL); if (rt_thread_self() == RT_NULL || (serial->parent.open_flag & RT_DEVICE_FLAG_STREAM)) { /* using poll tx when the scheduler not startup or in stream mode */ return _serial_poll_tx(dev, pos, buffer, size); } /* When serial transmit in tx_blocking mode, * if the activated mode is RT_TRUE, it will return directly */ if (tx_fifo->activated == RT_TRUE) return 0; tx_fifo->activated = RT_TRUE; /* Call the transmit interface for transmission */ rst = serial->ops->transmit(serial, (rt_uint8_t *)buffer, size, RT_SERIAL_TX_BLOCKING); /* Waiting for the transmission to complete */ rt_completion_wait(&(tx_fifo->tx_cpt), RT_WAITING_FOREVER); /* Inactive tx mode flag */ tx_fifo->activated = RT_FALSE; return rst; }
阻塞传输,顾名思义,就是要等数据传输完成后再去执行其他操作。在这里又分了两种情况,一种是在调度器没执行,也就是说,在rtthread还在系统初始化的过程中收发数据,此时采用poll的方式直接发送,而调度器起来后,其实就是等待驱动发过来的完成量信息即可。
_serial_fifo_tx_nonblocking
static rt_ssize_t _serial_fifo_tx_nonblocking(struct rt_device *dev, rt_off_t pos, const void *buffer, rt_size_t size) { struct rt_serial_device *serial; struct rt_serial_tx_fifo *tx_fifo; rt_base_t level; rt_size_t length; RT_ASSERT(dev != RT_NULL); if (size == 0) return 0; serial = (struct rt_serial_device *)dev; RT_ASSERT((serial != RT_NULL) && (buffer != RT_NULL)); tx_fifo = (struct rt_serial_tx_fifo *) serial->serial_tx; level = rt_hw_interrupt_disable(); if (tx_fifo->activated == RT_FALSE) { /* When serial transmit in tx_non_blocking mode, if the activated mode is RT_FALSE, * start copying data into the ringbuffer */ tx_fifo->activated = RT_TRUE; /* Copying data into the ringbuffer */ length = rt_ringbuffer_put(&(tx_fifo->rb), buffer, size); rt_hw_interrupt_enable(level); rt_uint8_t *put_ptr = RT_NULL; /* Get the linear length buffer from rinbuffer */ tx_fifo->put_size = rt_serial_get_linear_buffer(&(tx_fifo->rb), &put_ptr); /* Call the transmit interface for transmission */ serial->ops->transmit(serial, put_ptr, tx_fifo->put_size, RT_SERIAL_TX_NON_BLOCKING); /* In tx_nonblocking mode, there is no need to call rt_completion_wait() APIs to wait * for the rt_current_thread to resume */ return length; } /* If the activated mode is RT_TRUE, it means that serial device is transmitting, * where only the data in the ringbuffer and there is no need to call the transmit() API. * Note that this part of the code requires disable interrupts * to prevent multi thread reentrant */ /* Copying data into the ringbuffer */ length = rt_ringbuffer_put(&(tx_fifo->rb), buffer, size); rt_hw_interrupt_enable(level); return length; }
由于非阻塞,因此在实现时直接判断当前是否读写,如果不在读写,则直接启用transmit去传送。而在读写,则直接将数据放置于ringbuffer后返回,个人猜测写操作停止的逻辑肯定是ringbuffer中数据为空才这么操作的。
串口控制操作
static rt_err_t rt_serial_control(struct rt_device *dev, int cmd, void *args) { rt_err_t ret = RT_EOK; struct rt_serial_device *serial; RT_ASSERT(dev != RT_NULL); serial = (struct rt_serial_device *)dev; switch (cmd) { case RT_DEVICE_CTRL_SUSPEND: /* suspend device */ dev->flag |= RT_DEVICE_FLAG_SUSPENDED; break; case RT_DEVICE_CTRL_RESUME: /* resume device */ dev->flag &= ~RT_DEVICE_FLAG_SUSPENDED; break; case RT_DEVICE_CTRL_CONFIG: if (args != RT_NULL) { struct serial_configure *pconfig = (struct serial_configure *) args; if (((pconfig->rx_bufsz != serial->config.rx_bufsz) || (pconfig->tx_bufsz != serial->config.tx_bufsz)) && serial->parent.ref_count) { /*can not change buffer size*/ return -RT_EBUSY; } /* set serial configure */ serial->config = *pconfig; serial->ops->configure(serial, (struct serial_configure *) args); } break; case RT_DEVICE_CTRL_NOTIFY_SET: if (args) { rt_memcpy(&serial->rx_notify, args, sizeof(struct rt_device_notify)); } break; case RT_DEVICE_CTRL_CONSOLE_OFLAG: if (args) { *(rt_uint16_t*)args = RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX | RT_DEVICE_FLAG_STREAM; } break; #ifdef RT_USING_POSIX_STDIO #ifdef RT_USING_POSIX_TERMIOS case TCGETA: { struct termios *tio = (struct termios*)args; if (tio == RT_NULL) return -RT_EINVAL; tio->c_iflag = 0; tio->c_oflag = 0; tio->c_lflag = 0; /* update oflag for console device */ if (rt_console_get_device() == dev) tio->c_oflag = OPOST | ONLCR; /* set cflag */ tio->c_cflag = 0; if (serial->config.data_bits == DATA_BITS_5) tio->c_cflag = CS5; else if (serial->config.data_bits == DATA_BITS_6) tio->c_cflag = CS6; else if (serial->config.data_bits == DATA_BITS_7) tio->c_cflag = CS7; else if (serial->config.data_bits == DATA_BITS_8) tio->c_cflag = CS8; if (serial->config.stop_bits == STOP_BITS_2) tio->c_cflag |= CSTOPB; if (serial->config.parity == PARITY_EVEN) tio->c_cflag |= PARENB; else if (serial->config.parity == PARITY_ODD) tio->c_cflag |= (PARODD | PARENB); if (serial->config.flowcontrol == RT_SERIAL_FLOWCONTROL_CTSRTS) tio->c_cflag |= CRTSCTS; cfsetospeed(tio, _get_speed(serial->config.baud_rate)); } break; case TCSETAW: case TCSETAF: case TCSETA: { int baudrate; struct serial_configure config; struct termios *tio = (struct termios*)args; if (tio == RT_NULL) return -RT_EINVAL; config = serial->config; baudrate = _get_baudrate(cfgetospeed(tio)); config.baud_rate = baudrate; switch (tio->c_cflag & CSIZE) { case CS5: config.data_bits = DATA_BITS_5; break; case CS6: config.data_bits = DATA_BITS_6; break; case CS7: config.data_bits = DATA_BITS_7; break; default: config.data_bits = DATA_BITS_8; break; } if (tio->c_cflag & CSTOPB) config.stop_bits = STOP_BITS_2; else config.stop_bits = STOP_BITS_1; if (tio->c_cflag & PARENB) { if (tio->c_cflag & PARODD) config.parity = PARITY_ODD; else config.parity = PARITY_EVEN; } else config.parity = PARITY_NONE; if (tio->c_cflag & CRTSCTS) config.flowcontrol = RT_SERIAL_FLOWCONTROL_CTSRTS; else config.flowcontrol = RT_SERIAL_FLOWCONTROL_NONE; /* set serial configure */ serial->config = config; serial->ops->configure(serial, &config); } break; case TCFLSH: { int queue = (int)args; _tc_flush(serial, queue); } break; case TCXONC: break; #endif /*RT_USING_POSIX_TERMIOS*/ case TIOCSWINSZ: { struct winsize* p_winsize; p_winsize = (struct winsize*)args; rt_kprintf("\x1b[8;%d;%dt", p_winsize->ws_col, p_winsize->ws_row); } break; case TIOCGWINSZ: { struct winsize* p_winsize; p_winsize = (struct winsize*)args; if(rt_thread_self() != rt_thread_find(FINSH_THREAD_NAME)) { /* only can be used in tshell thread; otherwise, return default size */ p_winsize->ws_col = 80; p_winsize->ws_row = 24; } else { #include <shell.h> #define _TIO_BUFLEN 20 char _tio_buf[_TIO_BUFLEN]; unsigned char cnt1, cnt2, cnt3, i; char row_s[4], col_s[4]; char *p; rt_memset(_tio_buf, 0, _TIO_BUFLEN); /* send the command to terminal for getting the window size of the terminal */ rt_kprintf("\033[18t"); /* waiting for the response from the terminal */ i = 0; while(i < _TIO_BUFLEN) { _tio_buf[i] = finsh_getchar(); if(_tio_buf[i] != 't') { i ++; } else { break; } } if(i == _TIO_BUFLEN) { /* buffer overloaded, and return default size */ p_winsize->ws_col = 80; p_winsize->ws_row = 24; break; } /* interpreting data eg: "\033[8;1;15t" which means row is 1 and col is 15 (unit: size of ONE character) */ rt_memset(row_s,0,4); rt_memset(col_s,0,4); cnt1 = 0; while(cnt1 < _TIO_BUFLEN && _tio_buf[cnt1] != ';') { cnt1++; } cnt2 = ++cnt1; while(cnt2 < _TIO_BUFLEN && _tio_buf[cnt2] != ';') { cnt2++; } p = row_s; while(cnt1 < cnt2) { *p++ = _tio_buf[cnt1++]; } p = col_s; cnt2++; cnt3 = rt_strlen(_tio_buf) - 1; while(cnt2 < cnt3) { *p++ = _tio_buf[cnt2++]; } /* load the window size date */ p_winsize->ws_col = atoi(col_s); p_winsize->ws_row = atoi(row_s); #undef _TIO_BUFLEN } p_winsize->ws_xpixel = 0;/* unused */ p_winsize->ws_ypixel = 0;/* unused */ } break; case FIONREAD: { rt_size_t recved = 0; rt_base_t level; struct rt_serial_rx_fifo * rx_fifo = (struct rt_serial_rx_fifo *) serial->serial_rx; level = rt_hw_interrupt_disable(); recved = rt_ringbuffer_data_len(&(rx_fifo->rb)); rt_hw_interrupt_enable(level); *(rt_size_t *)args = recved; } break; #endif /* RT_USING_POSIX_STDIO */ default : /* control device */ ret = serial->ops->control(serial, cmd, args); break; } return ret; }
看似控制操作很多,实际上和V1一样,去掉几个目前学习来说无用宏包裹的部分看, 仅仅有那么几个入口:
1. 挂起/恢复串口:虽然写了这么个标志,但是没啥用,因为这标志压根没用到
2. 设置串口参数入口:功能同init中,个人理解为打开串口前的操作入口,即find,设置串口参数,打开串口
3. RT_DEVICE_CTRL_NOTIFY_SET:这接口居然还在,不过后面分析发现确实还有这接口的应用,只是优先级由原先的由notify就不上报rx_complete改为notify不影响rx_complete逻辑了
4. RT_DEVICE_CTRL_CONSOLE_OFLAG:也还在,作用未知
5. 其他入口:标准框架实现不了,驱动独有的设置入口,对应接口为驱动层的control
串口关闭操作
static rt_err_t rt_serial_rx_disable(struct rt_device *dev, rt_uint16_t rx_oflag) { struct rt_serial_device *serial; struct rt_serial_rx_fifo *rx_fifo; RT_ASSERT(dev != RT_NULL); serial = (struct rt_serial_device *)dev; #ifndef RT_USING_DEVICE_OPS dev->read = RT_NULL; #endif if (serial->serial_rx == RT_NULL) return RT_EOK; do { if (rx_oflag == RT_SERIAL_RX_NON_BLOCKING) { dev->open_flag &= ~ RT_SERIAL_RX_NON_BLOCKING; serial->ops->control(serial, RT_DEVICE_CTRL_CLR_INT, (void *)RT_SERIAL_RX_NON_BLOCKING); break; } dev->open_flag &= ~ RT_SERIAL_RX_BLOCKING; serial->ops->control(serial, RT_DEVICE_CTRL_CLR_INT, (void *)RT_SERIAL_RX_BLOCKING); } while (0); rx_fifo = (struct rt_serial_rx_fifo *)serial->serial_rx; RT_ASSERT(rx_fifo != RT_NULL); rt_free(rx_fifo); serial->serial_rx = RT_NULL; return RT_EOK; } static rt_err_t rt_serial_tx_disable(struct rt_device *dev, rt_uint16_t tx_oflag) { struct rt_serial_device *serial; struct rt_serial_tx_fifo *tx_fifo; RT_ASSERT(dev != RT_NULL); serial = (struct rt_serial_device *)dev; #ifndef RT_USING_DEVICE_OPS dev->write = RT_NULL; #endif if (serial->serial_tx == RT_NULL) return RT_EOK; tx_fifo = (struct rt_serial_tx_fifo *)serial->serial_tx; RT_ASSERT(tx_fifo != RT_NULL); do { if (tx_oflag == RT_SERIAL_TX_NON_BLOCKING) { dev->open_flag &= ~ RT_SERIAL_TX_NON_BLOCKING; serial->ops->control(serial, RT_DEVICE_CTRL_CLR_INT, (void *)RT_SERIAL_TX_NON_BLOCKING); break; } rt_completion_done(&(tx_fifo->tx_cpt)); dev->open_flag &= ~ RT_SERIAL_TX_BLOCKING; serial->ops->control(serial, RT_DEVICE_CTRL_CLR_INT, (void *)RT_SERIAL_TX_BLOCKING); } while (0); rt_free(tx_fifo); serial->serial_tx = RT_NULL; rt_memset(&serial->rx_notify, 0, sizeof(struct rt_device_notify)); return RT_EOK; } static rt_err_t rt_serial_close(struct rt_device *dev) { struct rt_serial_device *serial; RT_ASSERT(dev != RT_NULL); serial = (struct rt_serial_device *)dev; /* this device has more reference count */ if (dev->ref_count > 1) return -RT_ERROR; /* Disable serial receive mode. */ rt_serial_rx_disable(dev, dev->open_flag & (RT_SERIAL_RX_BLOCKING | RT_SERIAL_RX_NON_BLOCKING)); /* Disable serial tranmit mode. */ rt_serial_tx_disable(dev, dev->open_flag & (RT_SERIAL_TX_BLOCKING | RT_SERIAL_TX_NON_BLOCKING)); /* Clear the callback function */ serial->parent.rx_indicate = RT_NULL; serial->parent.tx_complete = RT_NULL; /* Call the control() API to close the serial device */ serial->ops->control(serial, RT_DEVICE_CTRL_CLOSE, RT_NULL); dev->flag &= ~RT_DEVICE_FLAG_ACTIVATED; return RT_EOK; }
V2版操作还是延续了V1版操作的做法,但是呢,把发送接收资源销毁封装到下一层函数中,而在发送函数中,那个被我理解成删除掉的rx_notify又被添加了,至于为什么放在这,而不是放在rt_serial_rx_disable中,就无从知晓了,个人只能猜测是写V2的人也不确定是否有用,但又不敢直接删除,就移到此处而未验证吧。
Add on
与V1类似,收发都需要给驱动的中断暴露一些接口,以便V2的逻辑正常运行,而在V2版的接口中,实际上暴露出去的接口与V1基本一致,具体实现如下:
void rt_hw_serial_isr(struct rt_serial_device *serial, int event) { RT_ASSERT(serial != RT_NULL); switch (event & 0xff) { /* Interrupt receive event */ case RT_SERIAL_EVENT_RX_IND: case RT_SERIAL_EVENT_RX_DMADONE: { struct rt_serial_rx_fifo *rx_fifo; rt_size_t rx_length = 0; rx_fifo = (struct rt_serial_rx_fifo *)serial->serial_rx; rt_base_t level; RT_ASSERT(rx_fifo != RT_NULL); /* If the event is RT_SERIAL_EVENT_RX_IND, rx_length is equal to 0 */ rx_length = (event & (~0xff)) >> 8; if (rx_length) { /* RT_SERIAL_EVENT_RX_DMADONE MODE */ level = rt_hw_interrupt_disable(); rt_serial_update_write_index(&(rx_fifo->rb), rx_length); rt_hw_interrupt_enable(level); } /* Get the length of the data from the ringbuffer */ rx_length = rt_ringbuffer_data_len(&rx_fifo->rb); if (rx_length == 0) break; if (serial->parent.open_flag & RT_SERIAL_RX_BLOCKING) { if (rx_fifo->rx_cpt_index && rx_length >= rx_fifo->rx_cpt_index ) { rx_fifo->rx_cpt_index = 0; rt_completion_done(&(rx_fifo->rx_cpt)); } } /* Trigger the receiving completion callback */ if (serial->parent.rx_indicate != RT_NULL) serial->parent.rx_indicate(&(serial->parent), rx_length); if (serial->rx_notify.notify) { serial->rx_notify.notify(serial->rx_notify.dev); } break; } /* Interrupt transmit event */ case RT_SERIAL_EVENT_TX_DONE: { struct rt_serial_tx_fifo *tx_fifo; rt_size_t tx_length = 0; tx_fifo = (struct rt_serial_tx_fifo *)serial->serial_tx; RT_ASSERT(tx_fifo != RT_NULL); /* Get the length of the data from the ringbuffer */ tx_length = rt_ringbuffer_data_len(&tx_fifo->rb); /* If there is no data in tx_ringbuffer, * then the transmit completion callback is triggered*/ if (tx_length == 0) { /* Trigger the transmit completion callback */ if (serial->parent.tx_complete != RT_NULL) serial->parent.tx_complete(&serial->parent, RT_NULL); /* Maybe some datas left in the buffer still need to be sent in block mode, * so tx_fifo->activated should be RT_TRUE */ if (serial->parent.open_flag & RT_SERIAL_TX_BLOCKING) { rt_completion_done(&(tx_fifo->tx_cpt)); } else { tx_fifo->activated = RT_FALSE; } break; } /* Call the transmit interface for transmission again */ /* Note that in interrupt mode, tx_fifo->buffer and tx_length * are inactive parameters */ serial->ops->transmit(serial, tx_fifo->buffer, tx_length, serial->parent.open_flag & ( \ RT_SERIAL_TX_BLOCKING | \ RT_SERIAL_TX_NON_BLOCKING)); break; } case RT_SERIAL_EVENT_TX_DMADONE: { struct rt_serial_tx_fifo *tx_fifo; tx_fifo = (struct rt_serial_tx_fifo *)serial->serial_tx; RT_ASSERT(tx_fifo != RT_NULL); tx_fifo->activated = RT_FALSE; /* Trigger the transmit completion callback */ if (serial->parent.tx_complete != RT_NULL) serial->parent.tx_complete(&serial->parent, RT_NULL); if (serial->parent.open_flag & RT_SERIAL_TX_BLOCKING) { rt_completion_done(&(tx_fifo->tx_cpt)); break; } rt_serial_update_read_index(&tx_fifo->rb, tx_fifo->put_size); /* Get the length of the data from the ringbuffer. * If there is some data in tx_ringbuffer, * then call the transmit interface for transmission again */ if (rt_ringbuffer_data_len(&tx_fifo->rb)) { tx_fifo->activated = RT_TRUE; rt_uint8_t *put_ptr = RT_NULL; /* Get the linear length buffer from rinbuffer */ tx_fifo->put_size = rt_serial_get_linear_buffer(&(tx_fifo->rb), &put_ptr); /* Call the transmit interface for transmission again */ serial->ops->transmit(serial, put_ptr, tx_fifo->put_size, RT_SERIAL_TX_NON_BLOCKING); } break; } default: break; } }
这个函数,需要在驱动函数对应的中断中去调用,以便满足串口框架上的各种实现。
总结
至此,最初版本的串口框架已经分析完毕,而分析完这个框架,我们会发现,相比较于V1版的接口,V2版的接口对驱动的要求与V1差异不算大,驱动仅仅需要将中断,DMA方式的读写控制入口改为阻塞非阻塞的入口即可。
另外,V2版驱动做了以下改进:
1. 使用ringbuffer替换queue,解决数据读写逻辑复杂的问题
2. 对上层暴露的接口变为阻塞和非阻塞,而不是该驱动内部维护的DMA,INT,poll方式,避免了内容过多的对应用暴露
3. 自旋锁的应用范围缩小,加快了调度速度
但V2版也带来了一个不好的点,也是我一直吐槽的点,开关全局中断的操作过于频繁。按照我的理解,此操作仅限于系统层和某些不得不关全局中断的场景才使用,但貌似在RTT中,这接口属于随意使用状态。