nuttx串口驱动框架设计
以STM32为例,来说明nuttx串口驱动框架设计:
先看通用串口驱动定义:
struct uart_buffer_s
{mutex_t lock; /* Used to control exclusive access to the buffer */volatile int16_t head; /* Index to the head [IN] index in the buffer */volatile int16_t tail; /* Index to the tail [OUT] index in the buffer */int16_t size; /* The allocated size of the buffer */FAR char *buffer; /* Pointer to the allocated buffer memory */
};
struct uart_dev_s
{...struct uart_buffer_s recv;
...
}
其中recv的的buffer初始化的时候,被初始化一块内存:
static char g_usart1rxbuffer[CONFIG_USART1_RXBUFSIZE];
static char g_usart1rxfifo[RXDMA_BUFFER_SIZE];
static struct up_dev_s g_usart1priv ={....recv ={.size = sizeof(g_usart1rxbuffer),.buffer = g_usart1rxbuffer,},.rxfifo = g_usart1rxfifo,...
}
接下里你是不是会想,如果芯片有DMA的话,把buffer内存给到DMA,如果没有DMA的话,每次读取寄存器复制到buffer中?
不好意思,不是。stm32的芯片驱动中又搞了一个FIFO,此FIFO是软件FIFO不是硬件FIFO,芯片是这么初始化的:
static int up_dma_setup(struct uart_dev_s *dev)
{
....rxdmacfg.paddr = priv->usartbase + STM32_USART_RDR_OFFSET;rxdmacfg.maddr = (uint32_t)priv->rxfifo;rxdmacfg.ndata = RXDMA_BUFFER_SIZE;rxdmacfg.cfg1 = SERIAL_RXDMA_CONTROL_WORD;rxdmacfg.cfg2 = 0;stm32_dmasetup(priv->rxdma, &rxdmacfg);
.....
}
就是说把FIFO的地址给到了DMA,先接收到FIFO中
接收中断回调
static void up_dma_rxcallback(DMA_HANDLE handle, uint8_t status, void *arg)
{...uart_recvchars(&priv->dev);...
}
void uart_recvchars(FAR uart_dev_t *dev)
{...while(底层有数据?){ch = uart_receive(dev, &status);dev->recv->buffer[索引++]=ch;注意这句话}...
}
其中uart_receive 是芯片驱动实现的:
static int up_dma_receive(struct uart_dev_s *dev, unsigned int *status)
{...返回FIFO[索引++];...
}
看到这里大家就明白了,FIFO作为DMA接收内存,然后复制给buffer。那么buffer到时候还会复制到user buffer中,数据复制了两次。
我看了nuttx对于STM32的芯片驱动实现,用了IDLE+DMA空闲中断实现不定长数据,但是仍然存在FIFO ->BUFFER->UserBuffer的场景。此实现包括所有的STM32芯片。
其他芯片的实现没有用IDLE,比如AT32,GD32这种(这些芯片都带IDLE)。
read(...user buffer..)
{while(等待信号量或者有数据){memcpy user buffer,串口驱动buffer,长度;}
}
对的。就是这么实现的。几乎所有的nuttx的串口驱动都是这么实现。包括GD32,AT32,RISCV架构的芯片。
我认为一个良好的设计:
read(...user buffer...)
{if(查询buffer是否可以作为串口的DMA(userbuffer)==是的,可以){up_dma_set_up(user buffer); 如果这一步,不涉及复制}else{tempbuffer=串口alloc(len);up_dma_set_up(user buffer);memcpy userbuffer temp buffer len;拷贝一次}
}
串口alloc(len)
{当前可能有几块内存,某些内存可作为串口DMA。找一找,返回;
}
up_dma_set_up(buffer)
{将buffer给DMA
}串口中断()
{uart_recvchars_dma(...,接收到的数据长度)
}