rt-thread icon indicating copy to clipboard operation
rt-thread copied to clipboard

serial_v2 框架中 DMA 发送完成逻辑处理的一个讨论

Open WKJay opened this issue 1 year ago • 1 comments

rt_hw_serial_isr 中的 RT_SERIAL_EVENT_TX_DMADONE 分支逻辑如下:

        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;
        }

可以看到尾部加入了再次发送的逻辑,本意是在 ringbuffer 中的数据未发送完成的情况下继续发送。这个逻辑没有任何问题,但是传输完成回调却是进入该分支就会被调用。个人认为虽然这个分支是 DMA 发送完成逻辑,但是实际上驱动在某些情况下(如 ringbuffer 有效数据在 buffer 尾部不连续)会分包发送,这种情况严格意义上并不能称之为发送完成,只能叫做“部分完成”。

虽然在多数情况下这个也不是什么问题,但如果遇到一些需要严格判断数据发送完成的情况下,如 RS485 需要在所有数据发送完成后才能操作使能脚,就会出现问题,即数据发到一半就触发完成回调,并在回调中操作使能脚,此时数据实际才发送了一部分。 仅个人观点,这里的发送完成回调既然是通知应用层的,那就应该是应用层的数据完全发送完成后才被调用,而不是应用层数据传输到驱动层后被驱动分包,然后驱动层的一包发送完成后就调用。

个人在使用时进行了如下修改:

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;
    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);
    }
    else
    {
        /* 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;
        }
    }
    break;
}

以上仅为个人的一个见解,作为讨论,如有错误,欢迎指正

WKJay avatar Apr 24 '24 10:04 WKJay