2 * Copyright (C) 2012 Michael Brown <mbrown@fensystems.co.uk>.
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
36 struct vdisk_file vdisk_files
[VDISK_MAX_FILES
];
39 * Read from virtual Master Boot Record
42 * @v count Number of blocks to read
45 static void vdisk_mbr ( uint64_t lba __unused
, unsigned int count __unused
,
47 struct vdisk_mbr
*mbr
= data
;
50 memset ( mbr
, 0, sizeof ( *mbr
) );
51 mbr
->partitions
[0].bootable
= VDISK_MBR_BOOTABLE
;
52 mbr
->partitions
[0].type
= VDISK_MBR_TYPE_FAT32
;
53 mbr
->partitions
[0].start
= VDISK_PARTITION_LBA
;
54 mbr
->partitions
[0].length
= VDISK_PARTITION_COUNT
;
55 mbr
->signature
= VDISK_MBR_SIGNATURE
;
56 mbr
->magic
= VDISK_MBR_MAGIC
;
60 * Read from virtual Volume Boot Record
63 * @v count Number of blocks to read
66 static void vdisk_vbr ( uint64_t lba __unused
, unsigned int count __unused
,
68 struct vdisk_vbr
*vbr
= data
;
71 memset ( vbr
, 0, sizeof ( *vbr
) );
72 vbr
->jump
[0] = VDISK_VBR_JUMP_WTF_MS
;
73 memcpy ( vbr
->oemid
, VDISK_VBR_OEMID
, sizeof ( vbr
->oemid
) );
74 vbr
->bytes_per_sector
= VDISK_SECTOR_SIZE
;
75 vbr
->sectors_per_cluster
= VDISK_CLUSTER_COUNT
;
76 vbr
->reserved_sectors
= VDISK_RESERVED_COUNT
;
78 vbr
->media
= VDISK_VBR_MEDIA
;
79 vbr
->sectors_per_track
= VDISK_SECTORS_PER_TRACK
;
80 vbr
->heads
= VDISK_HEADS
;
81 vbr
->hidden_sectors
= VDISK_VBR_LBA
;
82 vbr
->sectors
= VDISK_PARTITION_COUNT
;
83 vbr
->sectors_per_fat
= VDISK_SECTORS_PER_FAT
;
84 vbr
->root
= VDISK_ROOT_CLUSTER
;
85 vbr
->fsinfo
= VDISK_FSINFO_SECTOR
;
86 vbr
->backup
= VDISK_BACKUP_VBR_SECTOR
;
87 vbr
->signature
= VDISK_VBR_SIGNATURE
;
88 vbr
->serial
= VDISK_VBR_SERIAL
;
89 memcpy ( vbr
->label
, VDISK_VBR_LABEL
, sizeof ( vbr
->label
) );
90 memcpy ( vbr
->system
, VDISK_VBR_SYSTEM
, sizeof ( vbr
->system
) );
91 vbr
->magic
= VDISK_VBR_MAGIC
;
95 * Read from virtual FSInfo
98 * @v count Number of blocks to read
101 static void vdisk_fsinfo ( uint64_t lba __unused
, unsigned int count __unused
,
103 struct vdisk_fsinfo
*fsinfo
= data
;
105 /* Construct FSInfo */
106 memset ( fsinfo
, 0, sizeof ( *fsinfo
) );
107 fsinfo
->magic1
= VDISK_FSINFO_MAGIC1
;
108 fsinfo
->magic2
= VDISK_FSINFO_MAGIC2
;
109 fsinfo
->next_free
= VDISK_FSINFO_NEXT_FREE
;
110 fsinfo
->magic3
= VDISK_FSINFO_MAGIC3
;
114 * Read from virtual FAT
116 * @v lba Starting LBA
117 * @v count Number of blocks to read
118 * @v data Data buffer
120 static void vdisk_fat ( uint64_t lba
, unsigned int count
, void *data
) {
121 uint32_t *next
= data
;
124 uint32_t file_end_marker
;
127 /* Calculate window within FAT */
128 start
= ( ( lba
- VDISK_FAT_LBA
) *
129 ( VDISK_SECTOR_SIZE
/ sizeof ( *next
) ) );
130 end
= ( start
+ ( count
* ( VDISK_SECTOR_SIZE
/ sizeof ( *next
) ) ) );
133 /* Start by marking each cluster as chaining to the next */
134 for ( i
= start
; i
< end
; i
++ )
137 /* Add first-sector special values, if applicable */
139 next
[0] = ( ( VDISK_FAT_END_MARKER
& ~0xff ) |
141 for ( i
= 1; i
< ( VDISK_SECTOR_SIZE
/ sizeof ( *next
) ); i
++ )
142 next
[i
] = VDISK_FAT_END_MARKER
;
145 /* Add end-of-file markers, if applicable */
146 for ( i
= 0 ; i
< VDISK_MAX_FILES
; i
++ ) {
147 if ( vdisk_files
[i
].read
) {
148 file_end_marker
= ( VDISK_FILE_CLUSTER ( i
) +
149 ( ( vdisk_files
[i
].xlen
- 1 ) /
150 VDISK_CLUSTER_SIZE
) );
151 if ( ( file_end_marker
>= start
) &&
152 ( file_end_marker
< end
) ) {
153 next
[file_end_marker
] = VDISK_FAT_END_MARKER
;
160 * Initialise empty directory
162 * @v dir Virtual directory
163 * @ret dirent Starting (i.e. final) directory entry
165 static union vdisk_directory_entry
*
166 vdisk_empty_dir ( struct vdisk_directory
*dir
) {
169 /* Mark all entries as present and deleted */
170 memset ( dir
, 0, sizeof ( *dir
) );
171 for ( i
= 0 ; i
< VDISK_DIRENT_PER_SECTOR
; i
++ ) {
172 dir
->entry
[i
].deleted
= VDISK_DIRENT_DELETED
;
175 return &dir
->entry
[ VDISK_DIRENT_PER_SECTOR
- 1 ];
179 * Construct directory entry
181 * @v dirent Starting (i.e. final) directory entry
184 * @v attr File attributes
185 * @v cluster File starting cluster
186 * @ret next Next available directory entry
188 static union vdisk_directory_entry
*
189 vdisk_directory_entry ( union vdisk_directory_entry
*dirent
, const char *name
,
190 size_t len
,unsigned int attr
, uint32_t cluster
) {
191 union vdisk_directory_entry
*dos
= dirent
;
192 union vdisk_directory_entry
*lfn
= ( dos
- 1 );
193 uint8_t *checksum_data
;
195 unsigned int sequence
;
200 /* Populate directory entry (with invalid 8.3 filename) */
201 memset ( dos
->dos
.filename
.raw
, ' ', sizeof ( dos
->dos
.filename
.raw
) );
202 dos
->dos
.attr
= attr
;
204 dos
->dos
.cluster_high
= ( cluster
>> 16 );
205 dos
->dos
.cluster_low
= ( cluster
& 0xffff );
207 /* Calculate checksum of 8.3 filename */
209 checksum_data
= dos
->dos
.filename
.raw
;
210 for ( i
= 0 ; i
< sizeof ( dos
->dos
.filename
.raw
) ; i
++ ) {
211 checksum
= ( ( ( ( checksum
& 1 ) << 7 ) |
212 ( checksum
>> 1 ) ) +
213 *(checksum_data
++) );
216 /* Construct long filename record */
217 lfn_char
= &lfn
->lfn
.name_1
[0];
221 /* Initialise long filename, if necessary */
222 if ( lfn
->lfn
.attr
!= VDISK_LFN_ATTR
) {
223 lfn
->lfn
.sequence
= sequence
++;
224 memset ( lfn
->lfn
.name_1
, 0xff,
225 sizeof ( lfn
->lfn
.name_1
) );
226 lfn
->lfn
.attr
= VDISK_LFN_ATTR
;
227 lfn
->lfn
.checksum
= checksum
;
228 memset ( lfn
->lfn
.name_2
, 0xff,
229 sizeof ( lfn
->lfn
.name_2
) );
230 memset ( lfn
->lfn
.name_3
, 0xff,
231 sizeof ( lfn
->lfn
.name_3
) );
234 /* Add character to long filename */
240 /* Move to next character within long filename */
241 if ( lfn_char
== &lfn
->lfn
.name_1
[4] ) {
242 lfn_char
= &lfn
->lfn
.name_2
[0];
243 } else if ( lfn_char
== &lfn
->lfn
.name_2
[5] ) {
244 lfn_char
= &lfn
->lfn
.name_3
[0];
245 } else if ( lfn_char
== &lfn
->lfn
.name_3
[1] ) {
247 lfn_char
= &lfn
->lfn
.name_1
[0];
252 lfn
->lfn
.sequence
|= VDISK_LFN_END
;
258 * Read subdirectories from virtual root directory
260 * @v lba Starting LBA
261 * @v count Number of blocks to read
262 * @v data Data buffer
264 static void vdisk_root ( uint64_t lba __unused
, unsigned int count __unused
,
266 struct vdisk_directory
*dir
= data
;
267 union vdisk_directory_entry
*dirent
;
269 /* Construct subdirectories */
270 dirent
= vdisk_empty_dir ( dir
);
271 dirent
= vdisk_directory_entry ( dirent
, "BOOT", 0, VDISK_DIRECTORY
,
272 VDISK_BOOT_CLUSTER
);
273 dirent
= vdisk_directory_entry ( dirent
, "SOURCES", 0, VDISK_DIRECTORY
,
274 VDISK_SOURCES_CLUSTER
);
275 dirent
= vdisk_directory_entry ( dirent
, "EFI", 0, VDISK_DIRECTORY
,
280 * Read subdirectories from virtual boot directory
282 * @v lba Starting LBA
283 * @v count Number of blocks to read
284 * @v data Data buffer
286 static void vdisk_boot ( uint64_t lba __unused
, unsigned int count __unused
,
288 struct vdisk_directory
*dir
= data
;
289 union vdisk_directory_entry
*dirent
;
291 /* Construct subdirectories */
292 dirent
= vdisk_empty_dir ( dir
);
293 dirent
= vdisk_directory_entry ( dirent
, "FONTS", 0, VDISK_DIRECTORY
,
294 VDISK_FONTS_CLUSTER
);
295 dirent
= vdisk_directory_entry ( dirent
, "RESOURCES", 0,
297 VDISK_RESOURCES_CLUSTER
);
301 * Read subdirectories from virtual sources directory
303 * @v lba Starting LBA
304 * @v count Number of blocks to read
305 * @v data Data buffer
307 static void vdisk_sources ( uint64_t lba __unused
, unsigned int count __unused
,
309 struct vdisk_directory
*dir
= data
;
311 /* Construct subdirectories */
312 vdisk_empty_dir ( dir
);
316 * Read subdirectories from virtual fonts directory
318 * @v lba Starting LBA
319 * @v count Number of blocks to read
320 * @v data Data buffer
322 static void vdisk_fonts ( uint64_t lba __unused
, unsigned int count __unused
,
324 struct vdisk_directory
*dir
= data
;
326 /* Construct subdirectories */
327 vdisk_empty_dir ( dir
);
331 * Read subdirectories from virtual resources directory
333 * @v lba Starting LBA
334 * @v count Number of blocks to read
335 * @v data Data buffer
337 static void vdisk_resources ( uint64_t lba __unused
,
338 unsigned int count __unused
, void *data
) {
339 struct vdisk_directory
*dir
= data
;
341 /* Construct subdirectories */
342 vdisk_empty_dir ( dir
);
346 * Read subdirectories from virtual EFI directory
348 * @v lba Starting LBA
349 * @v count Number of blocks to read
350 * @v data Data buffer
352 static void vdisk_efi ( uint64_t lba __unused
, unsigned int count __unused
,
354 struct vdisk_directory
*dir
= data
;
355 union vdisk_directory_entry
*dirent
;
357 /* Construct subdirectories */
358 dirent
= vdisk_empty_dir ( dir
);
359 dirent
= vdisk_directory_entry ( dirent
, "BOOT", 0, VDISK_DIRECTORY
,
360 VDISK_BOOT_CLUSTER
);
361 dirent
= vdisk_directory_entry ( dirent
, "MICROSOFT", 0,
363 VDISK_MICROSOFT_CLUSTER
);
367 * Read subdirectories from virtual Microsoft directory
369 * @v lba Starting LBA
370 * @v count Number of blocks to read
371 * @v data Data buffer
373 static void vdisk_microsoft ( uint64_t lba __unused
,
374 unsigned int count __unused
, void *data
) {
375 struct vdisk_directory
*dir
= data
;
376 union vdisk_directory_entry
*dirent
;
378 /* Construct subdirectories */
379 dirent
= vdisk_empty_dir ( dir
);
380 dirent
= vdisk_directory_entry ( dirent
, "BOOT", 0, VDISK_DIRECTORY
,
381 VDISK_BOOT_CLUSTER
);
385 * Read files from virtual directory
387 * @v lba Starting LBA
388 * @v count Number of blocks to read
389 * @v data Data buffer
391 static void vdisk_dir_files ( uint64_t lba
, unsigned int count
, void *data
) {
392 struct vdisk_directory
*dir
;
393 union vdisk_directory_entry
*dirent
;
394 struct vdisk_file
*file
;
397 for ( ; count
; lba
++, count
--, data
+= VDISK_SECTOR_SIZE
) {
399 /* Initialise directory */
401 vdisk_empty_dir ( dir
);
402 dirent
= &dir
->entry
[ VDISK_DIRENT_PER_SECTOR
- 1 ];
405 idx
= VDISK_FILE_DIRENT_IDX ( lba
);
406 assert ( idx
< ( sizeof ( vdisk_files
) /
407 sizeof ( vdisk_files
[0] ) ) );
408 file
= &vdisk_files
[idx
];
412 /* Populate directory entry */
413 vdisk_directory_entry ( dirent
, file
->name
, file
->xlen
,
415 VDISK_FILE_CLUSTER ( idx
) );
420 * Read from virtual file (or empty space)
422 * @v lba Starting LBA
423 * @v count Number of blocks to read
424 * @v data Data buffer
426 static void vdisk_file ( uint64_t lba
, unsigned int count
, void *data
) {
427 struct vdisk_file
*file
;
434 /* Construct file portion */
435 file
= &vdisk_files
[ VDISK_FILE_IDX ( lba
) ];
436 offset
= VDISK_FILE_OFFSET ( lba
);
437 len
= ( count
* VDISK_SECTOR_SIZE
);
439 /* Copy any initialised-data portion */
440 copy_len
= ( ( offset
< file
->len
) ? ( file
->len
- offset
) : 0 );
441 if ( copy_len
> len
)
444 file
->read ( file
, data
, offset
, copy_len
);
446 /* Zero any uninitialised-data portion */
447 pad_len
= ( len
- copy_len
);
448 memset ( ( data
+ copy_len
), 0, pad_len
);
450 /* Patch any applicable portion */
451 patch_len
= ( ( offset
< file
->xlen
) ? ( file
->xlen
- offset
) : 0 );
452 if ( patch_len
> len
)
455 file
->patch ( file
, data
, offset
, patch_len
);
458 /** A virtual disk region */
459 struct vdisk_region
{
464 /** Number of blocks */
467 * Build data from this region
469 * @v start Starting LBA
470 * @v count Number of blocks to read
471 * @v data Data buffer
473 void ( * build
) ( uint64_t lba
, unsigned int count
, void *data
);
476 /** Define a virtual disk region */
477 #define VDISK_REGION( _name, _build, _lba, _count ) { \
484 /** Define a virtual disk directory region */
485 #define VDISK_DIRECTORY_REGION( _name, _build_subdirs, _lba ) { \
486 .name = _name " subdirs", \
489 .build = _build_subdirs, \
491 .name = _name " files", \
492 .lba = ( _lba + 1 ), \
493 .count = ( VDISK_CLUSTER_COUNT - 1 ), \
494 .build = vdisk_dir_files, \
497 /** Virtual disk regions */
498 static struct vdisk_region vdisk_regions
[] = {
499 VDISK_REGION ( "MBR", vdisk_mbr
,
500 VDISK_MBR_LBA
, VDISK_MBR_COUNT
),
501 VDISK_REGION ( "VBR", vdisk_vbr
,
502 VDISK_VBR_LBA
, VDISK_VBR_COUNT
),
503 VDISK_REGION ( "FSInfo", vdisk_fsinfo
,
504 VDISK_FSINFO_LBA
, VDISK_FSINFO_COUNT
),
505 VDISK_REGION ( "VBR Backup", vdisk_vbr
,
506 VDISK_BACKUP_VBR_LBA
, VDISK_BACKUP_VBR_COUNT
),
507 VDISK_REGION ( "FAT", vdisk_fat
,
508 VDISK_FAT_LBA
, VDISK_FAT_COUNT
),
509 VDISK_DIRECTORY_REGION ( "Root", vdisk_root
, VDISK_ROOT_LBA
),
510 VDISK_DIRECTORY_REGION ( "Boot", vdisk_boot
, VDISK_BOOT_LBA
),
511 VDISK_DIRECTORY_REGION ( "Sources", vdisk_sources
, VDISK_SOURCES_LBA
),
512 VDISK_DIRECTORY_REGION ( "Fonts", vdisk_fonts
, VDISK_FONTS_LBA
),
513 VDISK_DIRECTORY_REGION ( "Resources", vdisk_resources
,
514 VDISK_RESOURCES_LBA
),
515 VDISK_DIRECTORY_REGION ( "EFI", vdisk_efi
, VDISK_EFI_LBA
),
516 VDISK_DIRECTORY_REGION ( "Microsoft", vdisk_microsoft
,
517 VDISK_MICROSOFT_LBA
),
521 * Read from virtual disk
523 * @v lba Starting LBA
524 * @v count Number of blocks to read
525 * @v data Data buffer
527 void vdisk_read ( uint64_t lba
, unsigned int count
, void *data
) {
528 struct vdisk_region
*region
;
529 void ( * build
) ( uint64_t lba
, unsigned int count
, void *data
);
531 uint64_t start
= lba
;
532 uint64_t end
= ( lba
+ count
);
533 uint64_t frag_start
= start
;
537 uint64_t region_start
;
539 unsigned int frag_count
;
542 DBG2 ( "Read to %p from %#llx+%#x: ", data
, lba
, count
);
545 /* Initialise fragment to fill remaining space */
550 /* Truncate fragment and generate data */
551 file_idx
= VDISK_FILE_IDX ( frag_start
);
552 if ( file_idx
>= 0 ) {
554 /* Truncate fragment to end of file */
555 file_end
= VDISK_FILE_LBA ( file_idx
+ 1 );
556 if ( frag_end
> file_end
)
559 /* Generate data from file */
560 if ( file_idx
< VDISK_MAX_FILES
) {
561 name
= vdisk_files
[file_idx
].name
;
567 /* Truncate fragment to region boundaries */
568 for ( i
= 0 ; i
< ( sizeof ( vdisk_regions
) /
569 sizeof ( vdisk_regions
[0] ) ); i
++){
570 region
= &vdisk_regions
[i
];
571 region_start
= region
->lba
;
572 region_end
= ( region_start
+ region
->count
);
574 /* Avoid crossing start of any region */
575 if ( ( frag_start
< region_start
) &&
576 ( frag_end
> region_start
) ){
577 frag_end
= region_start
;
580 /* Ignore unless we overlap with this region */
581 if ( ( frag_start
>= region_end
) ||
582 ( frag_end
<= region_start
) ) {
586 /* Avoid crossing end of region */
587 if ( frag_end
> region_end
)
588 frag_end
= region_end
;
590 /* Found a suitable region */
592 build
= region
->build
;
597 /* Generate data from this region */
598 frag_count
= ( frag_end
- frag_start
);
599 DBG2 ( "%s%s (%#x)", ( ( frag_start
== start
) ? "" : ", " ),
600 ( name
? name
: "empty" ), frag_count
);
602 build ( frag_start
, frag_count
, data
);
604 memset ( data
, 0, ( frag_count
* VDISK_SECTOR_SIZE
) );
607 /* Move to next fragment */
608 frag_start
+= frag_count
;
609 data
+= ( frag_count
* VDISK_SECTOR_SIZE
);
611 } while ( frag_start
!= end
);
617 * Add file to virtual disk
620 * @v opaque Opaque token
622 * @v read Read data method
623 * @ret file Virtual file
625 struct vdisk_file
* vdisk_add_file ( const char *name
, void *opaque
, size_t len
,
626 void ( * read
) ( struct vdisk_file
*file
,
630 static unsigned int index
= 0;
631 struct vdisk_file
*file
;
634 if ( index
>= VDISK_MAX_FILES
)
635 die ( "Too many files\n" );
638 file
= &vdisk_files
[index
++];
639 snprintf ( file
->name
, sizeof ( file
->name
), "%s", name
);
640 file
->opaque
= opaque
;
644 DBG ( "Using %s via %p len %#zx\n", file
->name
, file
->opaque
,
653 * @v file Virtual file
654 * @v patch Patch method
656 void vdisk_patch_file ( struct vdisk_file
*file
,
657 void ( * patch
) ( struct vdisk_file
*file
, void *data
,
658 size_t offset
, size_t len
) ) {
660 /* Record patch method */
663 /* Allow patch method to update file length */
664 patch ( file
, NULL
, 0, 0 );