Yes, you are all right.
There is an extra overhead for storage needed to manage the OS File System. Also and the HW bad block replacement or other performance or caching buffers that eventually could be used is needed also. Not only the 100% of our data is stored in the disk, but additional data to manage all file structure and security.
Not sure if performance caching may use disk data blocks or are always implemented in additional RAM.
...
The spare storage allocation has nothing to do with the operating system, partitions, etc... It is purely an allocation of "dark" storage to increase the MTBF numbers. Something enterprise users pay for but cheap skate consumers croak about.
PCIe does allows for the firmware to know about partitions and drives but that's a logical layer applied to the PHY layer within the FW in the drive. PCI and SATA, et al, have no clue.
The PHY layer in the drive maintains the spare list and the tables of storage. This is the storage I'm talking about, and it can be an expensive one in the case of enterprise SSDs, a lot cheaper in magnetic disks.
...
Caching is indeed handled in DDR or the like. In SSDs a LOT of caching is required since the NAND blocks can only be written one block at a time... so to modify a block, you have to do a Read/Modify/Write action (*)... often what seems like a "simple" random write may involve several blocks, this is called
Write Amplification. So, the PHY layer may "aggregate the data" in an action across many blocks. The idea is to minimize disruption of the NAND since it wears it out.
With magnetic media you don't have those issues as you can random write stripes within a sector so for something that will see LOTS of random small writes, you're better off with HDDs than SSD.
Now, the issue with caching is loss of power. For this purpose SSDs and HDDs have a reasonable amount of capacitance and some means in the FW to rebuild the data in case of a power loss. A lot of work and money goes into that feature.
(*) Actually it's worse for an SSD.
1) Store new data for Block A into DDR
2) Read Block A into DDR
3) In DDR: Modify the Read Block A data with the new Write Data Block A
4) Fetch a block from the unallocated block list, Block B
5) Possibly Erase Block B
6) Write Block B with the modified Read Data Block A in DDR
7) Put Block A into the unallocated -not erased list (Might get erased by the
Garbage Collection).
8) Free the DDR buffers for the Read and Write Data
"Coding" is not an easy job, you see.