💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
网卡发送数据是通过low\_level\_output()函数实现的,该函数是一个底层驱动函数,这要求用户熟悉网卡底层特性,还要熟悉pbuf数据包。首先说说发送数据的过程,用户在应用层想要通过一个网卡发送数据,那么就要将数据传入LwIP内核中,经过内核的传输层封装、IP层封装等等,简单来说就是上层将要发送的数据层层封装,存储在pbuf数据包中,可能数据很大,想要多个pbuf才能存放得下,这时候pbuf就以链表的形式存在,当数据发送的时候,就要将属于一个数据包的数据全部发送出去,此处需要注意的是,属于同一个数据包中的所有数据都必须放在同一个以太网帧中发送。low\_level\_output()函数的实现具体见 ``` 1 static err_t low_level_output(struct netif *netif, struct pbuf *p) 2 { 3 err_t errval; 4 struct pbuf *q; 5 uint8_t *buffer = (uint8_t *)(heth.TxDesc->Buffer1Addr); 6 __IO ETH_DMADescTypeDef *DmaTxDesc; 7 uint32_t framelength = 0; 8 uint32_t bufferoffset = 0; 9 uint32_t byteslefttocopy = 0; 10 uint32_t payloadoffset = 0; 11 DmaTxDesc = heth.TxDesc; 12 bufferoffset = 0; 13 14 /* copy frame from pbufs to driver buffers */ 15 for (q = p; q != NULL; q = q->next) (1) 16 { 17 /* Is this buffer available? If not, goto error */ 18 if ((DmaTxDesc->Status & ETH_DMATXDESC_OWN) != (uint32_t)RESET) 19 { 20 errval = ERR_USE; 21 goto error; (2) 22 } 23 24 /* Get bytes in current lwIP buffer */ 25 byteslefttocopy = q->len; (3) 26 payloadoffset = 0; 27 28 /* Check if the length of data to copy is bigger than Tx buffer size*/ 29 while ( (byteslefttocopy + bufferoffset) > ETH_TX_BUF_SIZE ) (4) 30 { 31 /* Copy data to Tx buffer*/ 32 memcpy( (uint8_t*)((uint8_t*)buffer + bufferoffset), 33 (uint8_t*)((uint8_t*)q->payload + payloadoffset), 34 (ETH_TX_BUF_SIZE - bufferoffset) ); (5) 35 36 /* Point to next descriptor */ 37 DmaTxDesc = (ETH_DMADescTypeDef *)(DmaTxDesc->Buffer2NextDescAddr); (6) 38 39 /* Check if the buffer is available */ 40 if((DmaTxDesc->Status & ETH_DMATXDESC_OWN) != (uint32_t)RESET) 41 { 42 errval = ERR_USE; 43 goto error; (7) 44 } 45 46 buffer = (uint8_t *)(DmaTxDesc->Buffer1Addr); (8) 47 48 byteslefttocopy = byteslefttocopy - (ETH_TX_BUF_SIZE - bufferoffset); 49 payloadoffset = payloadoffset + (ETH_TX_BUF_SIZE - bufferoffset); 50 framelength = framelength + (ETH_TX_BUF_SIZE - bufferoffset); 51 bufferoffset = 0; (9) 52 } 53 54 /* Copy the remaining bytes */ 55 memcpy( (uint8_t*)((uint8_t*)buffer + bufferoffset), 56 (uint8_t*)((uint8_t*)q->payload + payloadoffset), byteslefttocopy ); 57 bufferoffset = bufferoffset + byteslefttocopy; 58 framelength = framelength + byteslefttocopy; (10) 59 } 60 61 /* Prepare transmit descriptors to give to DMA */ 62 HAL_ETH_TransmitFrame(&heth, framelength); (11) 63 64 errval = ERR_OK; 65 66 error: 67 68 /* When Transmit Underflow flag is set, clear it and issue a 69 Transmit Poll Demand to resume transmission */ 70 if ((heth.Instance->DMASR & ETH_DMASR_TUS) != (uint32_t)RESET) 71 { 72 /* Clear TUS ETHERNET DMA flag */ 73 heth.Instance->DMASR = ETH_DMASR_TUS; (12) 74 75 /* Resume DMA transmission*/ 76 heth.Instance->DMATPDR = 0; (13) 77 } 78 return errval; 79 } ``` (1):前面也说了,可能一个pbuf没法存储所有的数据,LwIP就会使用链表形式的pbuf将所有属于一个数据包的数据存储起来,那么在发送的时候就要变量这个pbuf链表,将所有数据都取出来。 (2):判断一下要发送数据的缓冲区可用吗?如果不可用,就跳转错误。 (3):获取pbuf中的数据长度。 (4):检查要拷贝的数据长度是否大于ETH_TX_BUF_SIZE的大小,当数据长度大于以太网发送缓冲区的时候,就需要分批次拷贝了。 (5):将pbuf中payload指向数据区域的数据拷贝到缓冲区,拷贝的大小就是缓冲区的大小。 (6):指向下一个发送的描述符(描述符是STM32以太网中的一种数据结构,此处了解一下即可)。 (7):检查一下要发送数据的缓冲区可用吗?如果不可用,就跳转错误。 (8):得到新的buff地址。 (9):计算还需要拷贝的长度、得到要偏移的数据地址、记录已经拷贝的长度,重新进行拷贝,直到要拷贝的数据长度小于ETH_TX_BUF_SIZE的大小。 (10):退出了while循环,拷贝剩余的数据,并记录拷贝了多大的数据。 (11):提供传输描述符及长度给DMA启动发送。 (12):当发送错误的时候,清除TUS ETHERNET DMA标志位。 (13):重新恢复DMA传输。