WIP: SD write debugging (store-TB TCI + sd-watch) and vvfat/ssi-sd fixes

Diagnostic: routes store-containing TBs through TCI and logs out-of-bounds
guest stores to isolate the SdSpiCard corruption. Also carries the FAT32
vvfat root/cluster fixes and ssi-sd command framing. Squash/split before
upstreaming.
This commit is contained in:
xteink
2026-07-20 17:48:33 -04:00
parent 64d7668b49
commit 543cebcb3a
11 changed files with 198 additions and 61 deletions
+91 -50
View File
@@ -749,6 +749,7 @@ static int read_directory(BDRVVVFATState* s, int mapping_index)
const char* dirname = mapping->path;
int first_cluster = mapping->begin;
int parent_index = mapping->info.dir.parent_mapping_index;
bool is_root = parent_index < 0;
mapping_t* parent_mapping = (mapping_t*)
(parent_index >= 0 ? array_get(&(s->mapping), parent_index) : NULL);
int first_cluster_of_parent = parent_mapping ? parent_mapping->begin : -1;
@@ -765,9 +766,9 @@ static int read_directory(BDRVVVFATState* s, int mapping_index)
}
i = mapping->info.dir.first_dir_index =
first_cluster == 0 ? 0 : s->directory.next;
is_root ? 0 : s->directory.next;
if (first_cluster != 0) {
if (!is_root) {
/* create the top entries of a subdirectory */
(void)create_short_and_long_name(s, i, ".", 1);
(void)create_short_and_long_name(s, i, "..", 1);
@@ -781,13 +782,14 @@ static int read_directory(BDRVVVFATState* s, int mapping_index)
int is_dot=!strcmp(entry->d_name,".");
int is_dotdot=!strcmp(entry->d_name,"..");
if (first_cluster == 0 && s->directory.next >= s->root_entries - 1) {
if (is_root && s->fat_type != 32 &&
s->directory.next >= s->root_entries - 1) {
fprintf(stderr, "Too many entries in root directory\n");
closedir(dir);
return -2;
}
if(first_cluster == 0 && (is_dotdot || is_dot))
if(is_root && (is_dotdot || is_dot))
continue;
buffer = g_malloc(length);
@@ -860,8 +862,7 @@ static int read_directory(BDRVVVFATState* s, int mapping_index)
memset(direntry,0,sizeof(direntry_t));
}
if (s->fat_type != 32 &&
mapping_index == 0 &&
if (s->fat_type != 32 && is_root &&
s->directory.next < s->root_entries) {
/* root directory */
int cur = s->directory.next;
@@ -877,20 +878,29 @@ static int read_directory(BDRVVVFATState* s, int mapping_index)
* 0x20 / s->cluster_size;
mapping->end = first_cluster;
direntry = array_get(&(s->directory), mapping->dir_index);
set_begin_of_direntry(direntry, mapping->begin);
if (!is_root) {
direntry = array_get(&(s->directory), mapping->dir_index);
set_begin_of_direntry(direntry, mapping->begin);
}
return 0;
}
static inline int32_t sector2cluster(BDRVVVFATState* s,off_t sector_num)
{
return (sector_num - s->offset_to_root_dir) / s->sectors_per_cluster;
off_t relative_sector = sector_num - s->offset_to_root_dir;
if (relative_sector < 0) {
return -1 - (-relative_sector - 1) / s->sectors_per_cluster;
}
return relative_sector / s->sectors_per_cluster
+ (s->fat_type == 32 ? 2 : 0);
}
static inline off_t cluster2sector(BDRVVVFATState* s, uint32_t cluster_num)
{
return s->offset_to_root_dir + s->sectors_per_cluster * cluster_num;
return s->offset_to_root_dir + s->sectors_per_cluster
* (cluster_num - (s->fat_type == 32 ? 2 : 0));
}
static int init_directories(BDRVVVFATState* s,
@@ -934,11 +944,12 @@ static int init_directories(BDRVVVFATState* s,
init_fat(s);
/* TODO: if there are more entries, bootsector has to be adjusted! */
s->root_entries = 0x02 * 0x10 * s->sectors_per_cluster;
s->root_entries = s->fat_type == 32 ? 0 :
0x02 * 0x10 * s->sectors_per_cluster;
s->cluster_count=sector2cluster(s, s->sector_count);
mapping = array_get_next(&(s->mapping));
mapping->begin = 0;
mapping->begin = s->fat_type == 32 ? 2 : 0;
mapping->dir_index = 0;
mapping->info.dir.parent_mapping_index = -1;
mapping->first_mapping_index = -1;
@@ -950,11 +961,10 @@ static int init_directories(BDRVVVFATState* s,
mapping->read_only = 0;
s->path = mapping->path;
for (i = 0, cluster = 0; i < s->mapping.next; i++) {
/* MS-DOS expects the FAT to be 0 for the root directory
* (except for the media byte). */
/* LATER TODO: still true for FAT32? */
int fix_fat = (i != 0);
for (i = 0, cluster = s->fat_type == 32 ? 2 : 0;
i < s->mapping.next; i++) {
/* FAT12/16 stores the root directory outside the cluster chain. */
int fix_fat = (i != 0 || s->fat_type == 32);
mapping = array_get(&(s->mapping), i);
if (mapping->mode & MODE_DIRECTORY) {
@@ -1026,22 +1036,35 @@ static int init_directories(BDRVVVFATState* s,
/* media descriptor: hard disk=0xf8, floppy=0xf0 */
bootsector->media_type = (s->offset_to_bootsector > 0 ? 0xf8 : 0xf0);
s->fat.pointer[0] = bootsector->media_type;
bootsector->sectors_per_fat=cpu_to_le16(s->sectors_per_fat);
bootsector->sectors_per_fat = s->fat_type == 32 ? 0 :
cpu_to_le16(s->sectors_per_fat);
bootsector->sectors_per_track = cpu_to_le16(secs);
bootsector->number_of_heads = cpu_to_le16(heads);
bootsector->hidden_sectors = cpu_to_le32(s->offset_to_bootsector);
bootsector->total_sectors=cpu_to_le32(s->sector_count>0xffff?s->sector_count:0);
/* LATER TODO: if FAT32, this is wrong */
/* drive_number: fda=0, hda=0x80 */
bootsector->u.fat16.drive_number = s->offset_to_bootsector == 0 ? 0 : 0x80;
bootsector->u.fat16.signature=0x29;
bootsector->u.fat16.id=cpu_to_le32(0xfabe1afd);
memcpy(bootsector->u.fat16.volume_label, s->volume_label,
sizeof(bootsector->u.fat16.volume_label));
memcpy(bootsector->u.fat16.fat_type,
s->fat_type == 12 ? "FAT12 " : "FAT16 ", 8);
if (s->fat_type == 32) {
bootsector->u.fat32.sectors_per_fat = cpu_to_le32(s->sectors_per_fat);
bootsector->u.fat32.first_cluster_of_root_dir = cpu_to_le32(2);
bootsector->u.fat32.info_sector = cpu_to_le16(0xffff);
bootsector->u.fat32.backup_boot_sector = cpu_to_le16(0xffff);
bootsector->u.fat32.drive_number = 0x80;
bootsector->u.fat32.signature = 0x29;
bootsector->u.fat32.id = cpu_to_le32(0xfabe1afd);
memcpy(bootsector->u.fat32.volume_label, s->volume_label,
sizeof(bootsector->u.fat32.volume_label));
memcpy(bootsector->u.fat32.fat_type, "FAT32 ", 8);
} else {
/* drive_number: fda=0, hda=0x80 */
bootsector->u.fat16.drive_number =
s->offset_to_bootsector == 0 ? 0 : 0x80;
bootsector->u.fat16.signature=0x29;
bootsector->u.fat16.id=cpu_to_le32(0xfabe1afd);
memcpy(bootsector->u.fat16.volume_label, s->volume_label,
sizeof(bootsector->u.fat16.volume_label));
memcpy(bootsector->u.fat16.fat_type,
s->fat_type == 12 ? "FAT12 " : "FAT16 ", 8);
}
bootsector->magic[0]=0x55; bootsector->magic[1]=0xaa;
return 0;
@@ -1139,6 +1162,7 @@ static int vvfat_open(BlockDriverState *bs, QDict *options, int flags,
{
BDRVVVFATState *s = bs->opaque;
int cyls, heads, secs;
uint64_t total_sectors;
bool floppy;
const char *dirname, *label;
QemuOpts *opts;
@@ -1205,7 +1229,9 @@ static int vvfat_open(BlockDriverState *bs, QDict *options, int flags,
switch (s->fat_type) {
case 32:
#ifndef EMSCRIPTEN
warn_report("FAT32 has not been tested. You are welcome to do so!");
#endif
break;
case 16:
case 12:
@@ -1219,8 +1245,17 @@ static int vvfat_open(BlockDriverState *bs, QDict *options, int flags,
s->bs = bs;
/* LATER TODO: if FAT32, adjust */
s->sectors_per_cluster=0x10;
#ifdef EMSCRIPTEN
if (!floppy && s->fat_type == 32) {
/* Browser SD Geometry - A larger cluster keeps the synthesized 32 GiB FAT near 4 MiB. */
s->sectors_per_cluster = 0x40;
total_sectors = 32ULL * 1024 * 1024 * 1024 / BDRV_SECTOR_SIZE;
} else
#endif
{
s->sectors_per_cluster = 0x10;
total_sectors = (uint64_t)cyls * heads * secs;
}
s->current_cluster=0xffffffff;
@@ -1232,8 +1267,8 @@ static int vvfat_open(BlockDriverState *bs, QDict *options, int flags,
DLOG(fprintf(stderr, "vvfat %s chs %d,%d,%d\n",
dirname, cyls, heads, secs));
s->sector_count = cyls * heads * secs - s->offset_to_bootsector;
bs->total_sectors = cyls * heads * secs;
s->sector_count = total_sectors - s->offset_to_bootsector;
bs->total_sectors = total_sectors;
if (qemu_opt_get_bool(opts, "rw", false)) {
if (!bdrv_is_read_only(bs)) {
@@ -1259,8 +1294,7 @@ static int vvfat_open(BlockDriverState *bs, QDict *options, int flags,
goto fail;
}
s->sector_count = s->offset_to_root_dir
+ s->sectors_per_cluster * s->cluster_count;
s->sector_count = cluster2sector(s, s->cluster_count);
/* Disable migration when vvfat is used rw */
if (s->qcow) {
@@ -1415,6 +1449,7 @@ read_cluster_directory:
if(lseek(s->current_fd, offset, SEEK_SET)!=offset)
return -3;
s->cluster=s->cluster_buffer;
memset(s->cluster, 0, s->cluster_size);
result=read(s->current_fd,s->cluster,s->cluster_size);
if(result<0) {
s->current_cluster = -1;
@@ -1523,8 +1558,9 @@ vvfat_read(BlockDriverState *bs, int64_t sector_num, uint8_t *buf, int nb_sector
} else {
uint32_t sector = sector_num - s->offset_to_root_dir,
sector_offset_in_cluster=(sector%s->sectors_per_cluster),
cluster_num=sector/s->sectors_per_cluster;
if(cluster_num > s->cluster_count || read_cluster(s, cluster_num) != 0) {
cluster_num=sector/s->sectors_per_cluster
+ (s->fat_type == 32 ? 2 : 0);
if(cluster_num >= s->cluster_count || read_cluster(s, cluster_num) != 0) {
/* LATER TODO: strict: return -1; */
memset(buf+i*0x200,0,0x200);
continue;
@@ -1780,7 +1816,8 @@ static int parse_short_name(BDRVVVFATState* s,
static inline uint32_t modified_fat_get(BDRVVVFATState* s,
unsigned int cluster)
{
if (cluster < s->last_cluster_of_root_directory) {
if (s->fat_type != 32 &&
cluster < s->last_cluster_of_root_directory) {
if (cluster + 1 == s->last_cluster_of_root_directory)
return s->max_fat_value;
else
@@ -2182,14 +2219,17 @@ DLOG(checkpoint());
mapping->mode |= MODE_DELETED;
}
used_clusters_count = check_directory_consistency(s, 0, s->path);
mapping_t *root_mapping = array_get(&(s->mapping), 0);
used_clusters_count = check_directory_consistency(s,
root_mapping->begin, s->path);
if (used_clusters_count <= 0) {
DLOG(fprintf(stderr, "problem in directory\n"));
return 0;
}
check = s->last_cluster_of_root_directory;
for (i = check; i < sector2cluster(s, s->sector_count); i++) {
check = s->fat_type == 32 ? 0 : s->last_cluster_of_root_directory;
for (i = s->fat_type == 32 ? 2 : check;
i < sector2cluster(s, s->sector_count); i++) {
if (modified_fat_get(s, i)) {
if(!s->used_clusters[i]) {
DLOG(fprintf(stderr, "FAT was modified (%d), but cluster is not used?\n", i));
@@ -2417,7 +2457,10 @@ static int coroutine_fn GRAPH_RDLOCK
commit_direntries(BDRVVVFATState* s, int dir_index, int parent_mapping_index)
{
direntry_t* direntry = array_get(&(s->directory), dir_index);
uint32_t first_cluster = dir_index == 0 ? 0 : begin_of_direntry(direntry);
bool is_root = dir_index == 0;
mapping_t *root_mapping = array_get(&(s->mapping), 0);
uint32_t first_cluster = is_root ? root_mapping->begin :
begin_of_direntry(direntry);
mapping_t* mapping = find_mapping_for_cluster(s, first_cluster);
int factor = 0x10 * s->sectors_per_cluster;
int old_cluster_count, new_cluster_count;
@@ -2440,7 +2483,7 @@ commit_direntries(BDRVVVFATState* s, int dir_index, int parent_mapping_index)
first_dir_index = current_dir_index;
mapping->info.dir.parent_mapping_index = parent_mapping_index;
if (first_cluster == 0) {
if (is_root && s->fat_type != 32) {
old_cluster_count = new_cluster_count =
s->last_cluster_of_root_directory;
} else {
@@ -2998,11 +3041,9 @@ DLOG(checkpoint());
*/
unsigned char *bootsector = s->first_sectors
+ s->offset_to_bootsector * 0x200;
/*
* LATER TODO: if FAT32, this is wrong (see init_directories(),
* which always creates a FAT16 bootsector)
*/
const int reserved1_offset = offsetof(bootsector_t, u.fat16.reserved1);
const int reserved1_offset = s->fat_type == 32 ?
offsetof(bootsector_t, u.fat32.reserved1) :
offsetof(bootsector_t, u.fat16.reserved1);
for (i = 0; i < 0x200; i++) {
if (i != reserved1_offset && bootsector[i] != buf[i]) {
@@ -3057,8 +3098,8 @@ DLOG(checkpoint());
begin = sector_num;
if (end > sector_num + nb_sectors)
end = sector_num + nb_sectors;
dir_index = mapping->dir_index +
0x10 * (begin - mapping->begin * s->sectors_per_cluster);
dir_index = mapping->info.dir.first_dir_index +
0x10 * (begin - cluster2sector(s, mapping->begin));
direntries = (direntry_t*)(buf + 0x200 * (begin - sector_num));
for (k = 0; k < (end - begin) * 0x10; k++) {
@@ -3093,7 +3134,7 @@ DLOG(fprintf(stderr, "Write to qcow backend: %d + %d\n", (int)sector_num, nb_sec
}
for (i = first_cluster; i <= last_cluster; i++) {
if (i >= 0) {
if (i >= 0 && i < sector2cluster(s, s->sector_count)) {
s->used_clusters[i] |= USED_ALLOCATED;
}
}