How to implement C language driver for SPI peripheral
The SPI protocol ties reading to writing. For each transmitted byte, a byte is received. Thus transmission and reception aren't independent and your SPI design should reflect that.
Therefore, you don't want a callback for each received byte as the link to the transmission is lost. Furthermore, it's far too slow to achieve data rates of 10 MHz or more.
In my experience, two approaches work:
Blocking byte operations
An Arduino like spi_transfer_byte() function: it sends a byte and returns the byte received at the same time. The function blocks until the byte has been received. Additionally, there are functions to start and end the SPI transaction (for the chip select signal). This pattern is easy to use, easy to implement, small in code size and often good enough.
Asynchronous SPI transactions
A higher-level API to execute entire SPI transactions. An array of bytes can be sent (similar to the way you have proposed). The function returns immediately as the transmission happens asynchronously. When the transmission has completed, a callback is called providing the received bytes (the same number as transmitted). For maximum performance, it should be possible to queue several SPI transactions. The callback should contain a reference to the submitted transaction.
This approach is more complex and usually requires DMA support for implementation (an interrupt for each byte is usually too slow). It can achieve higher throughput, is efficient for large SPI transactions (e.g. for LCD displays), does not block the rest of the code and is also suitable for RTOS.