What is actually wrong with the existing (ST) code assuming the buffers are explicitly 4-aligned?
That USB FIFO works explicitly in 32-bit units, so feeding it a byte array and a byte length (which is silently rounded up to a multiple of four), is just the wrong approach.
Just because you do 16 stores, does not mean you have to do exactly 16 loads, too.
I think it does.
Nope. Using the functions I showed, it does 64 byte loads on architectures where unaligned 32-bit loads are not supported.
That's not all. You can also do 15 32-bit reads, and 4 byte reads (for a total of 19 reads), and exactly 16 writes.
You do this by checking the lowest two bits of the source pointer, and pick one of four functions, one for each alignment (correct, off-by-1, off-by-2, off-by-3). The aligned one is trivial. For the unaligned ones, you combine two 32-bit registers into a 64-bit word, to use as a barrel shifter recovering the alignment.
Consider the case where the buffer address low bits are 11, meaning there is a single byte, followed by 15 aligned 32-bit words, followed by three bytes. You load the first byte to the low bits of the upper word, and the first full word to the low word:
|00000000 00000000 00000000 aaaaaaaa|bbbbbbbb cccccccc dddddddd eeeeeeee|and then rotate left by 24 bits, getting
|aaaaaaaa bbbbbbbb cccccccc dddddddd|eeeeeeee 00000000 00000000 00000000|You write the upper word, now perfectly aligned. Then, you rotate left by 8 bits:
|bbbbbbbb cccccccc dddddddd eeeeeeee|00000000 00000000 00000000 00000000|For the rest of the aligned 32-bit words, you repeat from the first step, reading the next aligned 32-bit word to the low part. For the three last bytes, you read them into the high bits of the low part instead:
|bbbbbbbb cccccccc dddddddd eeeeeeee|xxxxxxxx yyyyyyyy zzzzzzzz 00000000|and do a final rotate left by 24 bits, getting the final 32-bit word,
|eeeeeeee xxxxxxxx yyyyyyyy zzzzzzzz 00000000|00000000 00000000 00000000 00000000|writing the upper word. You're now done.
For an offset of
k bits, the shifts are
k and 32-
k bits. This works with even non-byte-aligned inputs, you see.
You do not need hardware bit rotate support or even 64-bit type support to do this, either; you can use
struct u32pair {
uint32_t lo;
uint32_t hi;
};
static inline u32pair u32pair_shift_left(struct u32pair p, uint_fast8_t n)
{
return (struct u32pair){ .hi = (p.hi << n) | (p.lo >> (32 - n)), .lo = p.lo << n);
}
See?
Yes, the cost is four shifts per 32-bit word output, plus some extra work for the initial and final unaligned bytes, but that wasn't the point. The point is that just because you need to write exactly 16 32-bit words from a possibly unaligned buffer, it does not mean you have to do 16 unaligned 32-bit reads from said unaligned buffer: you can do 15 aligned 32-bit reads and 4 byte reads instead, plus some extra work per 32-bit word.
(If you don't see, just say so: I can write the functions [and test and verify they work as I described, which is the annoying bit] for you to examine at leisure.)