There are 5 partitions, numbered 0 to 4. The following table shows the layout of these partitions in the flash memory. The offsets and sizes in this table are informative, not normative. It is recommended that the FLASHROM_INFO system call is always used to find the offset and size of a partition.
|
Partition 0 is a read-only partition containing factory settings such as region code and default language. Partition 1 only contains zeroes, and is not used for anything. The rest are read/write partitions using a block allocated scheme described below. Details on the contents of the individual partitions is given afterwards.
A block allocated partition is divided into numbered blocks 64 bytes each. The internal structure of such a partitions looks like the following:
|
Note that for simplicity, the calculation of the number of bitmap blocks is based on the total number of blocks, not the number of user blocks. That is, the number of bitmap blocks is partition_size / 32768 rounded upwards. This means that unnecessarily many bitmap blocks will actually be allocated for partitions of some sizes. For the partition sizes actually used on the Dreamcast (powers of two smaller than 16M) it doesn't make any difference though.
The header block is used to distinguish block allocated partitions from other types. It also contains version information for forwards compatibility.
This is the contents of the header:
|
For a partition to be considered a valid block allocated partition, the magic cookie string must be present, and the partition number field must match the actual number of the partition. The version number is used to check what version of the specification that the partition conforms to. Versions 0 and 1 are compatible with what is described on this page. Other versions may or may not be partially or fully incompatible.
Each bit in the bitmap correspond to one user block (although there may be some unused bits in the very end of the bitmap due to rounding). The bitmap block with the lowest physical number corresponds to the user data blocks with the lowest physical numbers, i.e. the first bitmap block (with physical block number M in the figure above) corresponds to the user data blocks at physical block number 1..512 (or 1..[M-1] if there are less than 512 user data blocks). Inside the bitmap blocks, the individual bits are arranged in a MSB fashion, so that the first user block represented by a bitmap block corresponds to the 128-bit of the first byte of that bitmap block. A bit value of "1" means a free block, "0" means that the block is allocated.
Before looking at the internal structure of a user block, a brief discussion on logical versus physical blocks is required. All references to block numbers so far have concerned physical blocks. These correspond directly to the offset from the start of the partition. That is, physical block K always starts K*64 bytes from the start of the partition. On the user level though, logical blocks are used instead. The mapping between logical and physical blocks is not fixed, but changes over time. For unused blocks, there is no mapping between logical and physical blocks at all.
The reason for the logical blocks is the way that the flash memory works. The flash memory is able to write a 0 to a bit previously containing a 1 at any time. In order to change a 0 into a 1 however, the entire partition has to be erased (turning it into all 1:s). Erasing the partition is an expensive operation as all data has to be written back afterwards, and each erase operation also reduces the lifetime of the chip (a flash memory chip is only guaranteed to hande a certain number of erases (typically several thousands though)). So in order to improve performance and chip lifetime, it is better to write new data to a previously unused section of the flash than trying to replace old data (requires an erase operation).
Thus, when new data gets written to a (logical) block, a new physical block is allocated and the new contents is written to this block. In this way, any number of physical blocks can be allocated to a given logical block. These physical blocks will contain various historical contents of the logical block. Only the most recently allocated of them (the one with the higest address) represents the corrent contents of the logical block. When no more unallocated physical blocks remain, the partition is erased, and each logical block is written back to one (1) physical block, leaving the rest of the physical user blocks unallocated.
It should be obvious from the above description that the bitmap is used to manage physical blocks. It does not reflect which logical blocks are in use. Neither does it give any hint which physical blocks contain up to date data, all allocated blocks must be considered to find the lastest contents of a particular logical block. Also note that since the bitmap used "1" to mark a block as free, and "0" to mark it as allocated, updating the bitmap to reflect the fact that another block has been allocated does not require an erase operation. As long as no physical blocks are freed (this only happens when we erase the partition anyway), the updated bitmap can simply be written back to the flash memory with the desired result.
Now for the internal structure of a user block:
|
Each block thus holds 60 bytes of data. In addition, the logical block number and a checksum of the block is stored. For each logical block, the last physical block which has both this particular logical block number and a correct checksum is considered to contain the data for the logical block. Logical block numbers start with 0, so although the physical blocks for the user data range from 1 to M-1, the logical block numbers will be in the range 0..[M-2]. (It is not allowed to have more logical blocks than physical blocks, because that would make it impossible to fit them all into physical blocks.)
The checksum is computed on the first 62 bytes of the block (the logical block number and the user data payload). The CRC calculation algorithm is as follows (C code):
int calcCRC(const unsigned char *buf, int size) { int i, c, n = 0xffff; for (i = 0; i < size; i++) { n ^= (buf[i]<<8); for (c = 0; c < 8; c++) if (n & 0x8000) n = (n << 1) ^ 4129; else n = (n << 1); } return (~n) & 0xffff; }(This is exactly the same CRC algorithm as for the IP0000.BIN checksum, except that the result is inverted.)
The logical block number and checksum fields are stored in little endian byte order.
Important: The SEGA system libraries require that physical blocks be allocated in a strictly sequential order. That is, it is illegal to allocate block K before any of the blocks 1..[K-1]. While it is unlikely that anyone should desire to use a different allocation order, I'd like to stress this rule anyway, since the system libraries will corrupt the partition unless it is obeyed. Also note that this allocation scheme makes it simple to determine the most recent version of a logical block: Higher physical block number <=> More recent contents.
Partition 3 contains data saved by games, such as video adjustment settings, which are local to a particular console. In order so that the games can themselves allocate block numbers, a scheme is used which allows each game to claim four consecutive logical blocks, amounting to 4*60=240 bytes of data. The first 112 bytes contain a fixed format header though, so at most 128 bytes of arbitrary data can be stored by each game.
The format of each file is as follows:
|
The checksum is calculated on the 110 bytes starting at offset $02 and ending at offset $6F, that is everything before the CRC itself except the initial two bytes of the header. The CRC algorithm is exactly the same as the one used for the physical blocks (see above).
|
There are 100 slots of 4 logical blocks each available to games in partition 3. The first slot is located at block 24-27, the second at block 28-31 etc, with the last slot at block 420-423. The remaining blocks, 0-23 and 424-509, seem to be reserved for other uses.
In order to allocate a slot, the game simply checks all slots in order until it finds one that doesn't have a valid header. The game can then save its own file in this slot (with a valid header of course). Naturally, the game must first check that it doesn't already have a slot allocated to it, by comparing the product number of all valid slots with its own.
If the game doesn't need all 120 bytes of user data, it doesn't have to write to all four blocks it has allocated. At least the first two must be written so that a valid header is established though. Be careful when checking for free slots not to assume that more than the first two blocks of a slot in use actually exist.