STM32 MAC driver optimization

This forum is dedicated to feedback, discussions about ongoing or future developments, ideas and suggestions regarding the ChibiOS projects are welcome. This forum is NOT for support.
iggarpe
Posts: 129
Joined: Sun Sep 30, 2012 8:32 pm

Re: STM32 MAC driver optimization

Post by iggarpe »

I also fixed the expression in the CRC checking offload but I am not sure how to test that, forgot how to patch lwIP about that.


See the "checksum options" section in lwipopts.h. There are a bunch of "CHECKSUM_..." defines that tell lwIP not to verify checksums on reception and generate them on transmission. If the STM32 MAC is doing its checksum offload duties correctly you can tell lwIP not to bother.

Making sure that the STM32 MAC checksum offload works is trivial for transmission because if it's not correctly generating and inserting the checksums the frames will be dropped by the remote host.

Reception however is a pain in the ass because you need to somehow generate in the remote host frames with a wrong checksum and verify that the MAC peripheral is marking them as erroneous and mac_lld is dropping them. I don't know how to do this in a linux box without some serious kernel hacking, maybe the easiest is just to use another STM32 system with checksum offload disabled and the lwIP checksum generation deliberately broken.

I would advice to just state clearly somewhere that STM32 MAC checksum offload is untested, and see of someone is up to testing it. Besides SRAM, my system is tight on performance too in corner cases so I might give it a try when all other functionality is done. I'll let you know.
iggarpe
Posts: 129
Joined: Sun Sep 30, 2012 8:32 pm

Re: STM32 MAC driver optimization

Post by iggarpe »

Not sure how that expression should be modified, the answer is in table 143 of the F4 RM, I will give it a closer look tomorrow.


In my RM (Doc ID 018909 Rev 3) it's table 163. You may want to grab the latest STM32F4 RM, I think they recently updated it with the newest additions to the family (STM32F42x and STM32F43x).

The lwip thread would get the buffers one by one and link them to a pbuf chain (do pbuf chains support pbufs of varying size? I hope so).


Being so popular, I think lwIP is seriously lacking proper documentation. For example I'm not quite sure I fully understand the difference between PBUF_REF and PBUF_ROM. Anyway, a pbuf is, by definition, a handler to a chunk of memory of any size. The pbuf structures themselves are allocated from a pool. The memory chunks pointed to by pbufs can come from several places: a lwIP managed malloc-like memory area (PBUF_RAM), a fixed size pool (PBUF_POOL) also managed by lwIP, or external (PBUF_REF and PBUF_ROM). In the later case if you also flag the pbuf as PBUF_FLAG_IS_CUSTOM, pbuf_free will typecast it to "struct pbuf_custom" and then call the callback function, which would allow you to free memory or something like that.

However, the custom pbuf cuntionality in lwIP seems to be intended to be used only internally by ip_frag, because the #define LWIP_SUPPORT_CUSTOM_PBUF is not in lwipopts.h but in pbuf.h and gets defined when IP_FRAG is enabled.

Options are:

1- Make IP_FRAG enabled mandatory with Chibi/OS.

2- Include a define LWIP_SUPPORT_CUSTOM_PBUF in lwipopts.h and patch pbuf.h to avoid a redefinition error:

#ifndef LWIP_SUPPORT_CUSTOM_PBUF
#define LWIP_SUPPORT_CUSTOM_PBUF (IP_FRAG && !IP_FRAG_USES_STATIC_BUF && !LWIP_NETIF_TX_SINGLE_PBUF)
#endif

For what is worth, I would go with (1) just to not have to maintain a patch to an external component.
iggarpe
Posts: 129
Joined: Sun Sep 30, 2012 8:32 pm

Re: STM32 MAC driver optimization

Post by iggarpe »

Both functions work using the current descriptors mechanism, basically the functions are list iterators. I think this should be sufficient to implement zero copy in both directions (even if transmission could be not possible with lwIP, to be verified).


For transmission guaranteeing that there are enough buffers for a full frame may be suboptimal and a bit tricky to implement. Ideally mac_lld_get_next_transmit_buffer would wait if necessary. Do you think it's feasible ?.

In any case, for STM32 MAC remember that you must set the OWN bit of the first DMA descriptor AFTER you've set it in all the other descriptors. Since the list is not double linked, I think you need to skip first and walk the list setting the OWN bit, then finall set the OWN bit of the first descriptor.

For reception I think mac_lld_get_next_receive_buffer is not enough, or more precisely, mac_lld_release_receive_descriptor will not cut it, because it operates on a chain of low level buffers.

You have no control on when the low level buffers will be freed because it will be the application layer doing it. And when you do it, lwIP will notify you via a callback (see struct pbuf_custom), but that callback is related to a low level buffer, not to a MACReceiveDescriptor structure (i.e. the chain of low level buffers).

I think we need a mac_lld API call to release one single buffer (and that, in the case of the STM32, it will just place the pointer into the next free DMA descriptor and set the OWN bit).

Obviously you'll get sequential notification of the whole chain being freed because that's exactly what pbuf_free does, walk the pbuf chain freeing one pbuf at a time. If you want to stick to mac_lld_release_receive descriptor you could always patch pbuf_free to check for a special PBUF flag and then make a single call to mac_lld_release_receive descriptor.

Again, I would prefer to just add mac_lld_release_receive_buffer to the API, because allows for absolute minimal (if any) modification of lwIP and reduces the maintenance burden. As I commented in another message, custom pbuf functionality (i.e. callback on free) is already there, you just need to either make sure it's enabled by making IP_FRAG mandatory, or allow to forcefully enable it with a #define and a small patch to pbuf.h to deal with a redefinition error.
User avatar
Giovanni
Site Admin
Posts: 14891
Joined: Wed May 27, 2009 8:48 am
Has thanked: 1202 times
Been thanked: 996 times

Re: STM32 MAC driver optimization

Post by Giovanni »

I still think that using large buffers is preferable because simplicity and code efficiency, you may try using fragments if you want but I would not put this ahead of the API definition.

Code: Select all

For transmission guaranteeing that there are enough buffers for a full frame may be suboptimal and a bit tricky to implement. Ideally mac_lld_get_next_transmit_buffer would wait if necessary. Do you think it's feasible ?.


It is not necessary, it is mac_lld_get_transmit_descriptor() that checks if there is a descriptor available, eventually the wait should go there but usually it is done in the higher levels.

Giovanni
User avatar
Giovanni
Site Admin
Posts: 14891
Joined: Wed May 27, 2009 8:48 am
Has thanked: 1202 times
Been thanked: 996 times

Re: STM32 MAC driver optimization

Post by Giovanni »

Hi,

I committed the new API implementation for the STM32 MAC driver, you should be able to implement you idea in lwipthread. Note that the current mode should be retained, you can check the macro MAC_USE_ZERO_COPY in order to select the zero-copy mode or use the old code. There should not be many differences anyway.

Giovanni
lmamakos
Posts: 20
Joined: Sun May 13, 2012 3:59 pm

Re: STM32 MAC driver optimization

Post by lmamakos »

iggarpe wrote:
I also fixed the expression in the CRC checking offload but I am not sure how to test that, forgot how to patch lwIP about that.


See the "checksum options" section in lwipopts.h. There are a bunch of "CHECKSUM_..." defines that tell lwIP not to verify checksums on reception and generate them on transmission. If the STM32 MAC is doing its checksum offload duties correctly you can tell lwIP not to bother.

Making sure that the STM32 MAC checksum offload works is trivial for transmission because if it's not correctly generating and inserting the checksums the frames will be dropped by the remote host.

Reception however is a pain in the ass because you need to somehow generate in the remote host frames with a wrong checksum and verify that the MAC peripheral is marking them as erroneous and mac_lld is dropping them. I don't know how to do this in a linux box without some serious kernel hacking, maybe the easiest is just to use another STM32 system with checksum offload disabled and the lwIP checksum generation deliberately broken.

I would advice to just state clearly somewhere that STM32 MAC checksum offload is untested, and see of someone is up to testing it. Besides SRAM, my system is tight on performance too in corner cases so I might give it a try when all other functionality is done. I'll let you know.


The UDP and TCP checksums are useful for more than detecting corrupted packet contents while in transit. And in the case where the transmission is a hop over a local ethernet where the frames are protected by a CRC, you're very unlikely to see corrupted packets being detected at the IP or TCP/UDP transport layers.

Those checksums, relatively weak as they are, are also valuable to detect software errors, random stores inside of buffers, reassembly errors, etc. In a relatively immature software stack, checksum offload is probably the last thing you want to enable.
iggarpe
Posts: 129
Joined: Sun Sep 30, 2012 8:32 pm

Re: STM32 MAC driver optimization

Post by iggarpe »

I committed the new API implementation for the STM32 MAC driver, you should be able to implement you idea in lwipthread. Note that the current mode should be retained, you can check the macro MAC_USE_ZERO_COPY in order to select the zero-copy mode or use the old code. There should not be many differences anyway.


Just checked it out and I see a few issues:

- Zero copy transmission can't be implemented with this api. Memory allocation happens in the application layer and propagates downwards. By the time we are to actually handle the data to the MAC driver the buffers are already allocated. mac_lld_get_next_transmit_buffer will provide you with a pointer to an existing buffer and then you need to copy there. No gain.

- Apparently mac_lld_get_next_transmit_buffer and mac_lld_get_next_receive_buffer do not advance rdp->physdesc to the next buffer. As of now it is ok because there is only one descriptor per frame, but I definitely want to reduce the buffer size so allocation granularity is smaller and memory is more efficiently used for small frames.

- It is missing a function to release a buffer. Again, as of now releasing a descriptor is the same than releasing a buffer, but if I want to use multiple buffers per frame on reception this is a no go. I need a buffer release function.

I've been working today in the implementation of multibuffer reception and works like a charm, no changes to the API (note that this is not zero copy, just an improvement on how the mac_lld buffer memory is used). Before I was using 8x1522 buffers and upon a sudden flowd the first dropped packet would be 29th, with the new implementation I use 48x256 buffers (about the same amount of memory than before) and the first dropped packet is 72th !!!

As I said, the API doesn't change, and best of all is that if you define the receive buffer size as before (1522) the code behaviour is EXACTLY the same than before, so I think this is a feature worth including in the STM32 mac_lld. Please let me know if you agree (note that because of the reasons I mentioned before the new zero copy API won't work with this multibuffer-frame feature, significative changes are needed).

My code is based on r4982 so I need to merge it with your recent changes before I can submit it (and BTW, how do I submit it, shall I post it here?).
Last edited by iggarpe on Sat Dec 29, 2012 8:05 pm, edited 1 time in total.
iggarpe
Posts: 129
Joined: Sun Sep 30, 2012 8:32 pm

Re: STM32 MAC driver optimization

Post by iggarpe »

Those checksums, relatively weak as they are, are also valuable to detect software errors, random stores inside of buffers, reassembly errors, etc. In a relatively immature software stack, checksum offload is probably the last thing you want to enable.


Mmm, I agree. The marginal performance gain is not worth the risk. Anyway eventually I'll enable and test it (but not for production) just to report Giovanni that it flies.
User avatar
Giovanni
Site Admin
Posts: 14891
Joined: Wed May 27, 2009 8:48 am
Has thanked: 1202 times
Been thanked: 996 times

Re: STM32 MAC driver optimization

Post by Giovanni »

Zero copy transmission can't be implemented with this api. Memory allocation happens in the application layer and propagates downwards. By the time we are to actually handle the data to the MAC driver the buffers are already allocated. mac_lld_get_next_transmit_buffer will provide you with a pointer to an existing buffer and then you need to copy there. No gain.


This is a problem in the application layer, it should be asking transmission buffers to the driver and write there directly. Assuming that a driver can transmit from a generic buffer is a huge assumption that does not consider things like, buffers in dedicated memory accessible by the MAC or alignment constraints. IMHO lwIP is flawed in this regards, buffers management should be in the driver. I'll keep it looking forward to the day we will have a dedicated TCP/IP stack, lwIP is not much appealing to me, way too complex for a "lw".

It is missing a function to release a buffer. Again, as of now releasing a descriptor is the same than releasing a buffer, but if I want to use multiple buffers per frame on reception this is a no go. I need a buffer release function.


Releasing a descriptor is meant to release all the buffers associated to the descriptor, how this is done is up to the implementation. I can't make a public API for an implementation-specific issue. Moreover, ther would be potential atomicity issues, would be OK to release buffers belonging to a descriptor one by one? in you case probably yes but you cannot do such assumptions.

Apparently mac_lld_get_next_transmit_buffer and mac_lld_get_next_receive_buffer do not advance rdp->physdesc to the next buffer. As of now it is ok because there is only one descriptor per frame, but I definitely want to reduce the buffer size so allocation granularity is smaller and memory is more efficiently used for small frames.


Correct, it is up to the implementation to do that, that implementation does not have multiple buffers.

Please let me know if you agree (note that because of the reasons I mentioned before the new zero copy API won't work with this multibuffer-frame feature, significative changes are needed).
My code is based on r4982 so I need to merge it with your recent changes before I can submit it (and BTW, how do I submit it, shall I post it here?).


Post it here, I will evaluate the changes however I am oriented keeping the code as simple as possible because I am unable to do serious testing on this driver. The STM32 has 16KB of dedicated memory for the MAC DMA so you can have plenty buffers without having to handle frames as fragments.

Giovanni
iggarpe
Posts: 129
Joined: Sun Sep 30, 2012 8:32 pm

Re: STM32 MAC driver optimization

Post by iggarpe »

This is a problem in the application layer, it should be asking transmission buffers to the driver and write there directly. Assuming that a driver can transmit from a generic buffer is a huge assumption that does not consider things like, buffers in dedicated memory accessible by the MAC or alignment constraints. IMHO lwIP is flawed in this regards, buffers management should be in the driver.


I totally agree. lwIP has a unified buffer management that makes no distinction on the purpose of a buffer when it's allocated, and I also consider it a flaw because it makes it totally impossible to implement what you describe, just because allocation is done at the application level and such a change would break all existing applications which is unacceptable.

That said, let's not forget that in a real time OS running in a microcontroller transmit performance is hardly going to be a problem in most, if not all applications. The bottleneck is going to be always somewhere else, and even if it is not, you have total control on the timing of your source of data so with some care you can synchronize things so you don't need large transmit buffer memory and buffer copying before handling the data to the MAC peripheral is just a tiny penalty.

Reception is a whole different story. There you're at the mercy of the network conditions, something you can't control in any possible way, so you must do your best to cope with hostile scenarios.

I'll keep it looking forward to the day we will have a dedicated TCP/IP stack, lwIP is not much appealing to me, way too complex for a "lw".


That would certainly fall into the cathegory of "reiventing the wheel". The value of lwIP is not only the work involved in creating it (though huge) but also its maturiry. BTW, I think there's also uIP (don't know much about it though).

Releasing a descriptor is meant to release all the buffers associated to the descriptor, how this is done is up to the implementation. I can't make a public API for an implementation-specific issue. Moreover, ther would be potential atomicity issues, would be OK to release buffers belonging to a descriptor one by one? in you case probably yes but you cannot do such assumptions.


Mmm... yes, you are right. With STM32 there is no problem, when a buffer is released you just assign it to a descriptor and mark it as owned by the DMA. That's all.

I agree this mechanism may not be feasible to implement in some architectures, but THEN AGAIN, for those architectures you can go with a single buffer per frame. Think about it. Maybe exposing a buffer release API (instead of descriptor release) is not that bad. If the architecture doesn't support it well, just don't implement the multibuffer receive and you're done. Buffer release becomes equal to descritptor release, and you leave the door open to implement multibuffer frame reception in architectures where it is easily doable, like the STM32. I assure you that in my case it has meant a HUGE difference in my ability to cope with those hostile situations I mentioned.

That said, there is also a way to go without per-buffer release:

I prefered to go that way because lwIP already implements the "custom pbuf" mechanism and in order to keep an hypothetical lwIP patch at a minimum. In this case, we would have to just use what lwIP provides, and the mechanism calls the release callback in a per-pbuf fashion when a pbuf chain is being released.

However, I just realized that though each mac_lld buffer ends up being placed into a separate pbuf, all those pbuf belong to a single chain and will remain together throughout their life cycle. That means that when one of them is being released it means it's being released from pbuf_free and ALL of them are being released as part of the same operation by pbuf_free. So what we can do in lwipthread when creating the pbuf chain is mark only the first (or last) pbuf as "custom pbuf" and place there a callback function that will just call the mac_lld release function. Note that no matter if you use the first or the last pbuf, it's up to pbuf_free the order of release (though I believe it goes from first to last), but also there is no harm in, for example, releasing ALL buffers of a descriptor when the first pbuf of a chain is being released by pbuf_free. Sure, the other buffers become immediately available to the MAC DMA while there are still "live" pbufs pointing to them, but those pbufs will live for just a few microseconds more while pbuf_free releases them and we are sure the associated buffers will not be accessed.

Post it here, I will evaluate the changes however I am oriented keeping the code as simple as possible because I am unable to do serious testing on this driver. The STM32 has 16KB of dedicated memory for the MAC DMA so you can have plenty buffers without having to handle frames as fragments.


"Plenty" is 10, and that's using the full 16K for receive buffers. I would not use "plenty". Note that AFIK USB/ethernet DMA can use directly those 16KB but also the other 112KB. The point is to have two separate areas with independent connections to the buffer matrix so they can be accessed simultaneously. The "natural" usage is to use the 16KB for USB/ethernet DMA and the 112KB for other stuff, but you could do it the other way around too. And of course you can place the DMA buffers in both chunks, and the only performance penalty would be bus contention due to the different bus masters competing for access to the SRAM (the CPU and the other two general purpose DMA, basically). The STM32F4 also has a 64KB chunk of SRAM called CCM, but it's connected directly to the CPU, not the bus matrix, and thus it can't be used for any DMA related operation.

See link below to download the patch (I'm including mac_lld.c, mac_ldd.h and also a unified patch so you can easily assess the modifications). I use to write lots of explicative comments so I think you'll find it easy to follow. I think the modifications do not add much complexity while meaning a huge performance improvement in the scenarios I'm facing and I've already described. Note also that if instead of small buffers you just use large (1522 bytes) receive buffers, the code behaves EXACTLY like the old code with no performance penaly whatsoever.

The transmit section hasn't been touched at all (though I might do shortly, now that I'm on this). The buffer size #define has been split to be able to specify different buffer sizes for transmision and reception.

https://dl.dropbox.com/u/35035041/stm32 ... eceive.zip
Post Reply