Implement a proper block layer with partition support.
authorBen Pfaff <blp@cs.stanford.edu>
Wed, 12 Nov 2008 06:19:01 +0000 (22:19 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Wed, 12 Nov 2008 06:19:01 +0000 (22:19 -0800)
This is in preparation for introducing new block devices, in
particular USB storage-based block devices so that Pintos can boot
from USB memory sticks on notebook and desktop PCs.

This block layer was inspired by one from Anthony Romano
<chz@vt.edu> but it has been extensively (perhaps entirely) rewritten.
Thus, bugs must certainly be blamed on the committer.

48 files changed:
doc/bibliography.texi
doc/filesys.texi
doc/installation.texi
doc/intro.texi
doc/reference.texi
doc/threads.texi
doc/userprog.texi
doc/vm.texi
src/Makefile.build
src/devices/block.c [new file with mode: 0644]
src/devices/block.h [new file with mode: 0644]
src/devices/disk.c [deleted file]
src/devices/disk.h [deleted file]
src/devices/ide.c [new file with mode: 0644]
src/devices/ide.h [new file with mode: 0644]
src/devices/partition.c [new file with mode: 0644]
src/devices/partition.h [new file with mode: 0644]
src/devices/shutdown.c
src/filesys/Make.vars
src/filesys/directory.c
src/filesys/directory.h
src/filesys/filesys.c
src/filesys/filesys.h
src/filesys/free-map.c
src/filesys/free-map.h
src/filesys/fsutil.c
src/filesys/inode.c
src/filesys/inode.h
src/lib/kernel/list.h
src/lib/stdio.c
src/lib/stdio.h
src/tests/Make.tests
src/tests/filesys/extended/Make.tests
src/tests/userprog/Make.tests
src/threads/Make.vars
src/threads/init.c
src/threads/init.h
src/threads/kernel.lds.S
src/threads/loader.S
src/threads/loader.h
src/threads/palloc.c
src/threads/start.S
src/userprog/Make.vars
src/utils/Pintos.pm [new file with mode: 0644]
src/utils/pintos
src/utils/pintos-mkdisk
src/utils/pintos-set-cmdline [new file with mode: 0644]
src/vm/Make.vars

index e998a075a0537787d3dbbc42570667a84b86541e..efdb05f44c46f7cb9b211296cfdfe8fb00f92b99 100644 (file)
@@ -110,6 +110,14 @@ Interface---DRAFT---24 April 2001}.  A draft of a revised version of
 The Open Group, @uref{http://www.unix.org/single_unix_specification/,
 , Single UNIX Specification V3}, 2001.
 
+@bibdfn{Partitions}
+A.@: E.@: Brouwer, @uref{specs/partitions/partition_tables.html, ,
+Minimal partition table specification}, 1999.
+
+@bibdfn{IntrList}
+R.@: Brown, @uref{http://www.ctyme.com/rbrown.htm, , Ralf Brown's
+Interrupt List}, 2000.
+
 @node Operating System Design References
 @section Operating System Design References
 
index eb2ba766bf8ec675d36746cf0bb268c06374328b..4c77d305fb3a162da64060e10a836770a0b67941 100644 (file)
@@ -174,8 +174,9 @@ rationale for it in your design documentation, and as long as it does
 not suffer from external fragmentation (as does the extent-based file
 system we provide).
 
-You can assume that the disk will not be larger than 8 MB.  You must
-support files as large as the disk (minus metadata).  Each inode is
+You can assume that the file system partition will not be larger than
+8 MB.  You must
+support files as large as the partition (minus metadata).  Each inode is
 stored in one disk sector, limiting the number of block pointers that it
 can contain.  Supporting 8 MB files will require you to implement
 doubly-indirect blocks.
@@ -189,7 +190,7 @@ every time a write is made off the end of the file.  Your file system
 must allow this.
 
 There should be no predetermined limit on the size of a file, except
-that a file cannot exceed the size of the disk (minus metadata).  This
+that a file cannot exceed the size of the file system (minus metadata).  This
 also applies to the root directory file, which should now be allowed
 to expand beyond its initial limit of 16 files.
 
@@ -456,10 +457,12 @@ modified by the reference solution.
  30 files changed, 2721 insertions(+), 286 deletions(-)
 @end verbatim
 
-@item Can @code{DISK_SECTOR_SIZE} change?
+@item Can @code{BLOCk_SECTOR_SIZE} change?
 
-No, @code{DISK_SECTOR_SIZE} is fixed at 512.  This is a fixed property
-of IDE disk hardware.
+No, @code{BLOCK_SECTOR_SIZE} is fixed at 512.  For IDE disks, this
+value is a fixed property of the hardware.  Other disks do not
+necessarily have a 512-byte sector, but for simplicity Pintos only
+supports those that do.
 @end table
 
 @menu
@@ -474,9 +477,10 @@ of IDE disk hardware.
 @table @b
 @item What is the largest file size that we are supposed to support?
 
-The disk we create will be 8 MB or smaller.  However, individual files
-will have to be smaller than the disk to accommodate the metadata.
-You'll need to consider this when deciding your inode organization.
+The file system partition we create will be 8 MB or smaller.  However,
+individual files will have to be smaller than the partition to
+accommodate the metadata.  You'll need to consider this when deciding
+your inode organization.
 @end table
 
 @node Subdirectories FAQ
index 0fc7b0673c7ab81ba06ce0ad823ff7b4585daecd..f6dfe44f428bd51e59bf6528e935191ca82c87b9 100644 (file)
@@ -79,8 +79,9 @@ described below (@pxref{Building Bochs for Pintos}).
 
 @item
 Install scripts from @file{src/utils}.  Copy @file{backtrace},
-@file{pintos}, @file{pintos-gdb}, @file{pintos-mkdisk} into the
-default @env{PATH}.
+@file{pintos}, @file{pintos-gdb}, @file{pintos-mkdisk},
+@file{pintos-set-cmdline}, and @file{Pintos.pm} into the default
+@env{PATH}.
 
 @item 
 Install @file{src/misc/gdb-macros} in a public location.  Then use a
index 6601adccbb1c65536b3cda1d976bb52724823c6b..ad5e28633fb49629e43ec6b34748dff1bbbc8e60 100644 (file)
@@ -161,21 +161,17 @@ single object file.  It contains debug information, so you can run
 GDB (@pxref{GDB}) or @command{backtrace} (@pxref{Backtraces}) on it.
 
 @item kernel.bin
-Memory image of the kernel.  These are the exact bytes loaded into
-memory to run the Pintos kernel.  To simplify loading, it is always
-padded out with zero bytes up to an exact multiple of 4 kB in
-size.
+Memory image of the kernel, that is, the exact bytes loaded into
+memory to run the Pintos kernel.  This is just @file{kernel.o} with
+debug information stripped out, which saves a lot of space, which in
+turn keeps the kernel from bumping up against a @w{512 kB} size limit
+imposed by the kernel loader's design.
 
 @item loader.bin
 Memory image for the kernel loader, a small chunk of code written in
 assembly language that reads the kernel from disk into memory and
 starts it up.  It is exactly 512 bytes long, a size fixed by the
 PC BIOS.
-
-@item os.dsk
-Disk image for the kernel, which is just @file{loader.bin} followed by
-@file{kernel.bin}.  This file is used as a ``virtual disk'' by the
-simulator.
 @end table
 
 Subdirectories of @file{build} contain object files (@file{.o}) and
@@ -207,8 +203,7 @@ right corner, or rerun the whole process by clicking on the ``Reset''
 button just to its left.  The other buttons are not very useful for our
 purposes.
 
-(If no window appeared at all, and you just got a terminal full of
-corrupt-looking text, then you're probably logged in remotely and X
+(If no window appeared at all, then you're probably logged in remotely and X
 forwarding is not set up correctly.  In this case, you can fix your X
 setup, or you can use the @option{-v} option to disable X output:
 @code{pintos -v -- run alarm-multiple}.)
@@ -513,10 +508,6 @@ The original structure and form of Pintos was inspired by the Nachos
 instructional operating system from the University of California,
 Berkeley (@bibref{Christopher}).
 
-A few of the Pintos source files are derived from code used in the
-Massachusetts Institute of Technology's 6.828 advanced operating systems
-course.  These files bear the original MIT license notice.
-
 The Pintos projects and documentation originated with those designed for
 Nachos by current and former CS 140 teaching assistants at Stanford
 University, including at least Yu Ping, Greg Hutchins, Kelly Shaw, Paul
index d4426106687d068480ec37874fb4255293478197..432bc8658d7bf41409d52919f7b7bf4dffe97ccf 100644 (file)
@@ -1,13 +1,12 @@
 @node Reference Guide
 @appendix Reference Guide
 
-This chapter is a reference for the Pintos code.  It covers the
-entire code base, but you'll only be using Pintos one part at a time,
-so you may find that you want to read each part as you work on the
+This chapter is a reference for the Pintos code.  The reference guide
+does not cover all of the code in Pintos, but it does cover those
+pieces that students most often find troublesome.  You may find that
+you want to read each part of the reference guide as you work on the
 project where it becomes important.
 
-(Actually, the reference guide is currently incomplete.)
-
 We recommend using ``tags'' to follow along with references to function
 and variable names (@pxref{Tags}).
 
@@ -30,7 +29,9 @@ initialization.
 
 @menu
 * Pintos Loader::               
-* Kernel Initialization::       
+* Low-Level Kernel Initialization::
+* High-Level Kernel Initialization::
+* Physical Memory Map::
 @end menu
 
 @node Pintos Loader
@@ -38,78 +39,94 @@ initialization.
 
 The first part of Pintos that runs is the loader, in
 @file{threads/loader.S}.  The PC BIOS loads the loader into memory.
-The loader, in turn, is responsible for initializing the CPU, loading
-the rest of Pintos into memory, and then jumping to its start.  It's
-not important to understand exactly what the loader does, but if
+The loader, in turn, is responsible for finding the kernel on disk,
+loading it into memory, and then jumping to its start.  It's
+not important to understand exactly how the loader works, but if
 you're interested, read on.  You should probably read along with the
 loader's source.  You should also understand the basics of the
 80@var{x}86 architecture as described by chapter 3, ``Basic Execution
 Environment,'' of @bibref{IA32-v1}.
 
-Because the PC BIOS loads the loader, the loader has to play by the
-BIOS's rules.  In particular, the BIOS only loads 512 bytes (one disk
-sector) into memory.  This is a severe restriction and it means that,
-practically speaking, the loader has to be written in assembly
-language.
-
-The Pintos loader first initializes the CPU.  The first important part of
-this is to enable the A20 line, that is, the CPU's address line
-numbered 20.  For historical reasons, PCs boot with this address
-line fixed at 0, which means that attempts to access memory beyond the
-first 1 MB (2 raised to the 20th power) will fail.  Pintos wants to
-access more memory than this, so we have to enable it.
-
-Next, the loader asks the BIOS for the PC's memory size.  Again for
-historical reasons, the function that we call in the BIOS to do this
-can only detect up to 64 MB of RAM, so that's the practical limit that
-Pintos can support.  The memory size is stashed away in a location in
-the loader that the kernel can read after it boots.
-
-Third, the loader creates a basic page table.  This page table maps
+The PC BIOS loads the loader from the first sector of the first hard
+disk, called the @dfn{master boot record} (MBR).  PC conventions
+reserve 64 bytes of the MBR for the partition table, and Pintos uses
+about 128 additional bytes for kernel command-line arguments.  This
+leaves a little over 300 bytes for the loader's own code.  This is a
+severe restriction that means, practically speaking, the loader must
+be written in assembly language.
+
+The Pintos loader and kernel don't have to be on the same disk, nor
+does is the kernel required to be in any particular location on a
+given disk.  The loader's first job, then, is to find the kernel by
+reading the partition table on each hard disk, looking for a bootable
+partition of the type used for a Pintos kernel.
+
+When the loader finds a bootable kernel partition, it reads the
+partition's contents into memory at physical address @w{128 kB}.  The
+kernel is at the beginning of the partition, which might be larger
+than necessary due to partition boundary alignment conventions, so the
+loader reads no more than @w{512 kB} (and the Pintos build process
+will refuse to produce kernels larger than that).  Reading more data
+than this would cross into the region from @w{640 kB} to @w{1 MB} that
+the PC architecture reserves for hardware and the BIOS, and a standard
+PC BIOS does not provide any means to load the kernel above @w{1 MB}.
+
+The loader's final job is to extract the entry point from the loaded
+kernel image and transfer control to it.  The entry point is not at a
+predictable location, but the kernel's ELF header contains a pointer
+to it.  The loader extracts the pointer and jumps to the location it
+points to.
+
+The Pintos kernel command line
+is stored in the boot loader.  The @command{pintos} program actually
+modifies a copy of the boot loader on disk each time it runs the kernel,
+inserting whatever command-line arguments the user supplies to the kernel,
+and then the kernel at boot time reads those arguments out of the boot
+loader in memory.  This is not an elegant solution, but it is simple
+and effective.
+
+@node Low-Level Kernel Initialization
+@subsection Low-Level Kernel Initialization
+
+The loader's last action is to transfer control to the kernel's entry
+point, which is @func{start} in @file{threads/start.S}.  The job of
+this code is to switch the CPU from legacy 16-bit ``real mode'' into
+the 32-bit ``protected mode'' used by all modern 80@var{x}86 operating
+systems.
+
+The startup code's first task is actually to obtain the machine's
+memory size, by asking the BIOS for the PC's memory size.  The
+simplest BIOS function to do this can only detect up to 64 MB of RAM,
+so that's the practical limit that Pintos can support.  The function
+stores the memory size, in pages, in global variable
+@code{init_ram_pages}.
+
+The first part of CPU initialization is to enable the A20 line, that
+is, the CPU's address line numbered 20.  For historical reasons, PCs
+boot with this address line fixed at 0, which means that attempts to
+access memory beyond the first 1 MB (2 raised to the 20th power) will
+fail.  Pintos wants to access more memory than this, so we have to
+enable it.
+
+Next, the loader creates a basic page table.  This page table maps
 the 64 MB at the base of virtual memory (starting at virtual address
 0) directly to the identical physical addresses.  It also maps the
 same physical memory starting at virtual address
 @code{LOADER_PHYS_BASE}, which defaults to @t{0xc0000000} (3 GB).  The
 Pintos kernel only wants the latter mapping, but there's a
 chicken-and-egg problem if we don't include the former: our current
-virtual address is roughly @t{0x7c00}, the location where the BIOS
-loaded us, and we can't jump to @t{0xc0007c00} until we turn on the
+virtual address is roughly @t{0x20000}, the location where the loader
+put us, and we can't jump to @t{0xc0020000} until we turn on the
 page table, but if we turn on the page table without jumping there,
 then we've just pulled the rug out from under ourselves.
 
 After the page table is initialized, we load the CPU's control
-registers to turn on protected mode and paging, and then we set up the
-segment registers.  We aren't yet equipped to handle interrupts in
-protected mode, so we disable interrupts.
-
-Finally it's time to load the kernel from disk.  We use a simple but
-inflexible method to do this: we program the IDE disk
-controller directly.  We assume that the kernel is stored starting
-from the second sector of the first IDE disk (the first sector normally
-contains the boot loader).  We also assume that the BIOS has
-already set up the IDE controller for us.  We read
-@code{KERNEL_LOAD_PAGES} pages of data (4 kB per page) from the disk directly
-into virtual memory, starting @code{LOADER_KERN_BASE} bytes past
-@code{LOADER_PHYS_BASE}, which by default means that we load the
-kernel starting 1 MB into physical memory.
-
-Then we jump to the start of the compiled kernel image.  Using the
-``linker script'' in @file{threads/kernel.lds.S}, the kernel has
-arranged to begin with the assembly module
-@file{threads/start.S}.  This assembly module just calls
-@func{main}, which never returns.
-
-There's one more trick: the Pintos kernel command line
-is stored in the boot loader.  The @command{pintos} program actually
-modifies a copy of the boot loader on disk each time it runs the kernel,
-putting
-in whatever command line arguments the user supplies to the kernel,
-and then the kernel at boot time reads those arguments out of the boot
-loader in memory.  This is not an elegant solution, but it is simple
-and effective.
+registers to turn on protected mode and paging, and set up the segment
+registers.  We aren't yet equipped to handle interrupts in protected
+mode, so we disable interrupts.  The final step is to call @func{main}.
 
-@node Kernel Initialization
-@subsection Kernel Initialization
+@node High-Level Kernel Initialization
+@subsection High-Level Kernel Initialization
 
 The kernel proper starts with the @func{main} function.  The
 @func{main} function is written in C, as will be most of the code we
@@ -124,17 +141,14 @@ These are usually named @func{@var{module}_init}, where
 module's source code, and @file{@var{module}.h} is the module's
 header.
 
-First we initialize kernel RAM in @func{ram_init}.  The first step
-is to clear out the kernel's so-called ``BSS'' segment.  The BSS is a
+The first step in @func{main} is to call @func{bss_init}, which clears
+out the kernel's ``BSS'', which is the traditional name for a
 segment that should be initialized to all zeros.  In most C
 implementations, whenever you
 declare a variable outside a function without providing an
 initializer, that variable goes into the BSS.  Because it's all zeros, the
 BSS isn't stored in the image that the loader brought into memory.  We
-just use @func{memset} to zero it out.  The other task of
-@func{ram_init} is to read out the machine's memory size from where
-the loader stored it and put it into the @code{init_ram_pages} variable for
-later use.
+just use @func{memset} to zero it out.
 
 Next, @func{main} calls @func{read_command_line} to break the kernel command
 line into arguments, then @func{parse_options} to read any options at
@@ -179,7 +193,7 @@ possible, so we use
 @func{timer_calibrate} calibrates the timer for accurate short delays.
 
 If the file system is compiled in, as it will starting in project 2, we
-initialize the disks with @func{disk_init}, then the
+initialize the IDE disks with @func{ide_init}, then the
 file system with @func{filesys_init}.
 
 Boot is complete, so we print a message.
@@ -193,6 +207,34 @@ call @func{power_off} to terminate the machine simulator.  Otherwise,
 @func{main} calls @func{thread_exit}, which allows any other running
 threads to continue running.
 
+@node Physical Memory Map
+@subsection Physical Memory Map
+
+@multitable {@t{00000000}--@t{00000000}} {Hardware} {Some much longer explanatory text}
+@headitem Memory Range
+@tab Owner
+@tab Contents
+
+@item @t{00000000}--@t{000003ff} @tab CPU @tab Real mode interrupt table.
+@item @t{00000400}--@t{000005ff} @tab BIOS @tab Miscellaneous data area.
+@item @t{00000600}--@t{00007bff} @tab --- @tab ---
+@item @t{00007c00}--@t{00007dff} @tab Pintos @tab Loader.
+@item @t{0000e000}--@t{0000efff} @tab Pintos 
+@tab Stack for loader; kernel stack and @struct{thread} for initial
+kernel thread.
+@item @t{0000f000}--@t{0000ffff} @tab Pintos
+@tab Page directory for startup code.
+@item @t{00010000}--@t{00020000} @tab Pintos
+@tab Page tables for startup code.
+@item @t{00020000}--@t{0009ffff} @tab Pintos
+@tab Kernel code, data, and uninitialized data segments.
+@item @t{000a0000}--@t{000bffff} @tab Video @tab VGA display memory.
+@item @t{000c0000}--@t{000effff} @tab Hardware 
+@tab Reserved for expansion card RAM and ROM.
+@item @t{000f0000}--@t{000fffff} @tab BIOS @tab ROM BIOS.
+@item @t{00100000}--@t{03ffffff} @tab Pintos @tab Dynamic memory allocation.
+@end multitable
+
 @node Threads
 @section Threads
 
@@ -556,7 +598,7 @@ synchronization primitives to help out.
 * Semaphores::                  
 * Locks::                       
 * Monitors::                    
-* Optimization Barriers::             
+* Optimization Barriers::
 @end menu
 
 @node Disabling Interrupts
@@ -1236,7 +1278,8 @@ at once.
 
 The page allocator divides the memory it allocates into two pools,
 called the kernel and user pools.  By default, each pool gets half of
-system memory, but this can be changed with the @option{-ul} kernel
+system memory above @w{1 MB}, but the division can be changed with the
+@option{-ul} kernel
 command line
 option (@pxref{Why PAL_USER?}).  An allocation request draws from one
 pool or the other.  If one pool becomes empty, the other may still
index d90b43895396a0ec81c3ff9c4ae91515572522ac..c1f3bfcd62c2caf43a0b1cbf1118b0cf8ffdb62f 100644 (file)
@@ -113,27 +113,30 @@ code to look at.
 @item loader.S
 @itemx loader.h
 The kernel loader.  Assembles to 512 bytes of code and data that the
-PC BIOS loads into memory and which in turn loads the kernel into
-memory, does basic processor initialization, and jumps to the
-beginning of the kernel.  @xref{Pintos Loader}, for details. You should
-not need to look at this code or modify it.
+PC BIOS loads into memory and which in turn finds the kernel on disk,
+loads it into memory, and jumps to @func{start} in @file{start.S}.
+@xref{Pintos Loader}, for details.  You should not need to look at
+this code or modify it.
+
+@item start.S
+Does basic setup needed for memory protection and 32-bit
+operation on 80@var{x}86 CPUs.  Unlike the loader, this code is
+actually part of the kernel.  @xref{Low-Level Kernel Initialization},
+for details.
 
 @item kernel.lds.S
 The linker script used to link the kernel.  Sets the load address of
-the kernel and arranges for @file{start.S} to be at the very beginning
+the kernel and arranges for @file{start.S} to be near the beginning
 of the kernel image.  @xref{Pintos Loader}, for details. Again, you
 should not need to look at this code
 or modify it, but it's here in case you're curious.
 
-@item start.S
-Jumps to @func{main}.
-
 @item init.c
 @itemx init.h
 Kernel initialization, including @func{main}, the kernel's ``main
 program.''  You should look over @func{main} at least to see what
 gets initialized.  You might want to add your own initialization code
-here.  @xref{Kernel Initialization}, for details.
+here.  @xref{High-Level Kernel Initialization}, for details.
 
 @item thread.c
 @itemx thread.h
@@ -220,10 +223,23 @@ Serial port driver.  Again, @func{printf} calls this code for you,
 so you don't need to do so yourself.
 It handles serial input by passing it to the input layer (see below).
 
-@item disk.c
-@itemx disk.h
-Supports reading and writing sectors on up to 4 IDE disks.  This won't
-actually be used until project 2.
+@item block.c
+@itemx block.h
+An abstraction layer for @dfn{block devices}, that is, random-access,
+disk-like devices that are organized as arrays of fixed-size blocks.
+Out of the box, Pintos supports two types of block devices: IDE disks
+and partitions.  Block devices, regardless of type, won't actually be
+used until project 2.
+
+@item ide.c
+@itemx ide.h
+Supports reading and writing sectors on up to 4 IDE disks.
+
+@item partition.c
+@itemx partition.h
+Understands the structure of partitions on disks, allowing a single
+disk to be carved up into multiple regions (partitions) for
+independent use.
 
 @item kbd.c
 @itemx kbd.h
index 9d80e375669819c5a43da14ae4009c85f7d344f8..cf01158423f9540ce0d91bf7aa3c745d00d71709 100644 (file)
@@ -183,14 +183,16 @@ threads that have it open, until the last one closes it.  @xref{Removing
 an Open File}, for more information.
 @end itemize
 
-You need to be able to create simulated disks.  The
-@command{pintos-mkdisk} program provides this functionality.  From the
-@file{userprog/build} directory, execute @code{pintos-mkdisk fs.dsk@tie{}2}.
-This command creates a 2 MB simulated disk named @file{fs.dsk}.  Then
-format the disk by passing @option{-f -q} on the kernel's command
-line: @code{pintos -f -q}.  The @option{-f} option causes the disk to be
-formatted, and @option{-q} causes Pintos to exit as soon as the format
-is done.
+You need to be able to create a simulated disk with a file system
+partition.  The @command{pintos-mkdisk} program provides this
+functionality.  From the @file{userprog/build} directory, execute
+@code{pintos-mkdisk filesys.dsk --filesys-size=2}.  This command
+creates a simulated disk named @file{filesys.dsk} that contains a @w{2
+MB} Pintos file system partition.  Then format the file system
+partition by passing @option{-f -q} on the kernel's command line:
+@code{pintos -f -q}.  The @option{-f} option causes the file system to
+be formatted, and @option{-q} causes Pintos to exit as soon as the
+format is done.
 
 You'll need a way to copy files in and out of the simulated file system.
 The @code{pintos} @option{-p} (``put'') and @option{-g} (``get'')
@@ -205,20 +207,19 @@ commands for copying files out of a VM are similar, but substitute
 
 Incidentally, these commands work by passing special commands
 @command{extract} and @command{append} on the kernel's command line and copying
-to and from a special simulated ``scratch'' disk.  If you're very
+to and from a special simulated ``scratch'' partition.  If you're very
 curious, you can look at the @command{pintos} script as well as
 @file{filesys/fsutil.c} to learn the implementation details.
 
-Here's a summary of how to create and format a disk, copy the
-@command{echo} program into the new disk, and then run @command{echo},
-passing argument @code{x}.  (Argument passing won't work until
-you implemented it.)  It assumes
-that you've already built the
-examples in @file{examples} and that the current directory is
-@file{userprog/build}:
+Here's a summary of how to create a disk with a file system partition,
+format the file system, copy the @command{echo} program into the new
+disk, and then run @command{echo}, passing argument @code{x}.
+(Argument passing won't work until you implemented it.)  It assumes
+that you've already built the examples in @file{examples} and that the
+current directory is @file{userprog/build}:
 
 @example
-pintos-mkdisk fs.dsk 2
+pintos-mkdisk filesys.dsk --filesys-size=2
 pintos -f -q
 pintos -p ../../examples/echo -a echo -- -q
 pintos -q run 'echo x'
@@ -227,19 +228,20 @@ pintos -q run 'echo x'
 The three final steps can actually be combined into a single command:
 
 @example
-pintos-mkdisk fs.dsk 2
+pintos-mkdisk filesys.dsk --filesys-size=2
 pintos -p ../../examples/echo -a echo -- -f -q run 'echo x'
 @end example
 
 If you don't want to keep the file system disk around for later use or
 inspection, you can even combine all four steps into a single command.
-The @code{--fs-disk=@var{n}} option creates a temporary disk
+The @code{--filesys-size=@var{n}} option creates a temporary file
+system partition
 approximately @var{n} megabytes in size just for the duration of the
 @command{pintos} run.  The Pintos automatic test suite makes extensive
 use of this syntax:
 
 @example
-pintos --fs-disk=2 -p ../../examples/echo -a echo -- -f -q run 'echo x'
+pintos --filesys-size=2 -p ../../examples/echo -a echo -- -f -q run 'echo x'
 @end example
 
 You can delete a file from the Pintos file system using the @code{rm
@@ -272,11 +274,11 @@ for Pintos.  (We've provided compilers and linkers that should do just
 fine.)
 
 You should realize immediately that, until you copy a
-test program to the simulated disk, Pintos will be unable to do
+test program to the simulated file system, Pintos will be unable to do
 useful work.  You won't be able to do
-interesting things until you copy a variety of programs to the disk.
-You might want to create a clean reference disk and copy that
-over whenever you trash your @file{fs.dsk} beyond a useful state,
+interesting things until you copy a variety of programs to the file system.
+You might want to create a clean reference file system disk and copy that
+over whenever you trash your @file{filesys.dsk} beyond a useful state,
 which may happen occasionally while debugging.
 
 @node Virtual Memory Layout
@@ -852,7 +854,7 @@ modified by the reference solution.
 
 @item The kernel always panics when I run @code{pintos -p @var{file} -- -q}.
 
-Did you format the disk (with @samp{pintos -f})?
+Did you format the file system (with @samp{pintos -f})?
 
 Is your file name too long?  The file system limits file names to 14
 characters.  A command like @samp{pintos -p ../../examples/echo -- -q}
@@ -873,6 +875,8 @@ Files are written under the name you refer to them, by default, so in
 this case the file copied in would be named @file{../file}.  You
 probably want to run @code{pintos -p ../file -a file --} instead.
 
+You can list the files in your file system with @code{pintos -q ls}.
+
 @item All my user programs die with page faults.
 
 This will happen if you haven't implemented argument passing
index fdbc5c68c492b2900745d29ad6d40cc9ab211080..ab64a4de57f4b60a09ad35fc26c17e6029128169 100644 (file)
@@ -49,10 +49,10 @@ files or in files introduced in earlier projects.
 You will probably be encountering just a few files for the first time:
 
 @table @file
-@item devices/disk.h
-@itemx devices/disk.c
-Provides access to the physical disk, abstracting away the rather awful
-IDE interface.  You will use this interface to access the swap disk.
+@item devices/block.h
+@itemx devices/block.c
+Provides sector-based read and write access to block device.  You will
+use this interface to access the swap partition as a block device.
 @end table
 
 @node Memory Terminology
@@ -162,8 +162,8 @@ address, on the right.
 @node Swap Slots
 @subsubsection Swap Slots
 
-A @dfn{swap slot} is a continuous, page-size region of disk space on the
-swap disk.  Although hardware limitations dictating the placement of
+A @dfn{swap slot} is a continuous, page-size region of disk space in the
+swap partition.  Although hardware limitations dictating the placement of
 slots are looser than for pages and frames, swap slots should be
 page-aligned because there is no downside in doing so.
 
@@ -377,17 +377,19 @@ to work with accessed and dirty bits.
 
 The swap table tracks in-use and free swap slots.  It should allow
 picking an unused swap slot for evicting a page from its frame to the
-swap disk.  It should allow freeing a swap slot when its page is read
+swap partition.  It should allow freeing a swap slot when its page is read
 back or the process whose page was swapped is terminated.
 
-You may use the disk on interface @code{hd1:1} as the swap disk, using
-the disk interface prototyped in @code{devices/disk.h}.  From the
+You may use the @code{BLOCK_SWAP} block device for swapping, obtaining
+the @struct{block} that represents it by calling @func{block_get_role}.
+From the
 @file{vm/build} directory, use the command @code{pintos-mkdisk swap.dsk
-@var{n}} to create an @var{n} MB swap disk named @file{swap.dsk}.
-Afterward, @file{swap.dsk} will automatically be attached as
-@code{hd1:1} when you run @command{pintos}.  Alternatively, you can tell
+--swap-size=@var{n}} to create an disk named @file{swap.dsk} that
+contains a @var{n}-MB swap partition.
+Afterward, @file{swap.dsk} will automatically be attached as an extra disk
+when you run @command{pintos}.  Alternatively, you can tell
 @command{pintos} to use a temporary @var{n}-MB swap disk for a single
-run with @option{--swap-disk=@var{n}}.
+run with @option{--swap-size=@var{n}}.
 
 Swap slots should be allocated lazily, that is, only when they are
 actually required by eviction.  Reading data pages from the executable
@@ -485,7 +487,7 @@ little as possible about how to do things.  Instead we will focus on
 what functionality we require your OS to support.  We will expect
 you to come up with a design that makes sense.  You will have the
 freedom to choose how to handle page faults, how to organize the swap
-disk, how to implement paging, etc.
+partition, how to implement paging, etc.
 
 @menu
 * Project 3 Design Document::   
@@ -534,7 +536,7 @@ variables' values:
 @itemize @bullet
 @item
 If @code{page_read_bytes} equals @code{PGSIZE}, the page should be demand
-paged from disk on its first access.
+paged from the underlying file on its first access.
 
 @item
 If @code{page_zero_bytes} equals @code{PGSIZE}, the page does not need to
@@ -545,7 +547,7 @@ first page fault.
 @item
 Otherwise, neither @code{page_read_bytes} nor @code{page_zero_bytes}
 equals @code{PGSIZE}.  In this case, an initial part of the page is to
-be read from disk and the remainder zeroed.
+be read from the underlying file and the remainder zeroed.
 @end itemize
 
 @node Stack Growth
@@ -619,7 +621,8 @@ mapped from.
 
 If the file's length is not a multiple of @code{PGSIZE}, then some
 bytes in the final mapped page ``stick out'' beyond the end of the
-file.  Set these bytes to zero when the page is faulted in from disk,
+file.  Set these bytes to zero when the page is faulted in from the
+file system,
 and discard them when the page is written back to disk.
 
 If successful, this function returns a ``mapping ID'' that
@@ -739,7 +742,7 @@ kernel functions need to obtain memory.
 You can layer some other allocator on top of @func{palloc_get_page} if
 you like, but it should be the underlying mechanism.
 
-Also, you can use the @option{-ul} option to @command{pintos} to limit
+Also, you can use the @option{-ul} kernel command-line option to limit
 the size of the user pool, which makes it easy to test your VM
 implementation with various user memory sizes.
 @end table
index b73320579ecc342f0b28b0f8bc78bb3ea513de3b..e997d2787543cb4860edff352660dea9ae350bcf 100644 (file)
@@ -2,17 +2,18 @@
 
 SRCDIR = ../..
 
-all: os.dsk
+all: kernel.bin loader.bin
 
 include ../../Make.config
 include ../Make.vars
 include ../../tests/Make.tests
 
 # Compiler and assembler options.
-os.dsk: CPPFLAGS += -I$(SRCDIR)/lib/kernel
+kernel.bin: CPPFLAGS += -I$(SRCDIR)/lib/kernel
 
 # Core kernel.
-threads_SRC  = threads/init.c          # Main program.
+threads_SRC  = threads/start.S         # Startup code.
+threads_SRC += threads/init.c          # Main program.
 threads_SRC += threads/thread.c                # Thread management core.
 threads_SRC += threads/switch.S                # Thread switch routine.
 threads_SRC += threads/interrupt.c     # Interrupt core.
@@ -20,7 +21,6 @@ threads_SRC += threads/intr-stubs.S   # Interrupt stubs.
 threads_SRC += threads/synch.c         # Synchronization.
 threads_SRC += threads/palloc.c                # Page allocator.
 threads_SRC += threads/malloc.c                # Subpage allocator.
-threads_SRC += threads/start.S         # Startup code.
 
 # Device driver code.
 devices_SRC  = devices/pit.c           # Programmable interrupt timer chip.
@@ -28,7 +28,9 @@ devices_SRC += devices/timer.c                # Periodic timer device.
 devices_SRC += devices/kbd.c           # Keyboard device.
 devices_SRC += devices/vga.c           # Video device.
 devices_SRC += devices/serial.c                # Serial port device.
-devices_SRC += devices/disk.c          # IDE disk device.
+devices_SRC += devices/block.c         # Block device abstraction layer.
+devices_SRC += devices/partition.c     # Partition block device.
+devices_SRC += devices/ide.c           # IDE disk block device.
 devices_SRC += devices/input.c         # Serial and keyboard input.
 devices_SRC += devices/intq.c          # Interrupt queue.
 devices_SRC += devices/rtc.c           # Real-time clock.
@@ -81,24 +83,23 @@ kernel.o: threads/kernel.lds.s $(OBJECTS)
        $(LD) -T $< -o $@ $(OBJECTS)
 
 kernel.bin: kernel.o
-       $(OBJCOPY) -O binary -R .note -R .comment -S $< $@.tmp
-       dd if=$@.tmp of=$@ bs=4096 conv=sync
-       rm $@.tmp
+       $(OBJCOPY) -R .note -R .comment -S $< $@
 
-threads/loader.o: threads/loader.S kernel.bin
-       $(CC) -c $< -o $@ $(ASFLAGS) $(CPPFLAGS) $(DEFINES) -DKERNEL_LOAD_PAGES=`perl -e 'print +(-s "kernel.bin") / 4096;'`
+threads/loader.o: threads/loader.S
+       $(CC) -c $< -o $@ $(ASFLAGS) $(CPPFLAGS) $(DEFINES)
 
 loader.bin: threads/loader.o
-       $(LD) -N -e start -Ttext 0x7c00 --oformat binary -o $@ $<
+       $(LD) -N -e 0 -Ttext 0x7c00 --oformat binary -o $@ $<
 
-os.dsk: loader.bin kernel.bin
+os.dsk: kernel.bin
        cat $^ > $@
 
 clean::
        rm -f $(OBJECTS) $(DEPENDS) 
        rm -f threads/loader.o threads/kernel.lds.s threads/loader.d
+       rm -f kernel.bin.tmp
        rm -f kernel.o kernel.lds.s
-       rm -f kernel.bin loader.bin os.dsk
+       rm -f kernel.bin loader.bin
        rm -f bochsout.txt bochsrc.txt
        rm -f results grade
 
diff --git a/src/devices/block.c b/src/devices/block.c
new file mode 100644 (file)
index 0000000..452c433
--- /dev/null
@@ -0,0 +1,223 @@
+#include "devices/block.h"
+#include <list.h>
+#include <string.h>
+#include <stdio.h>
+#include "devices/ide.h"
+#include "threads/malloc.h"
+
+/* A block device. */
+struct block
+  {
+    struct list_elem list_elem;         /* Element in all_blocks. */
+
+    char name[16];                      /* Block device name. */
+    enum block_type type;                /* Type of block device. */
+    block_sector_t size;                 /* Size in sectors. */
+
+    const struct block_operations *ops;  /* Driver operations. */
+    void *aux;                          /* Extra data owned by driver. */
+
+    unsigned long long read_cnt;        /* Number of sectors read. */
+    unsigned long long write_cnt;       /* Number of sectors written. */
+  };
+
+/* List of all block devices. */
+static struct list all_blocks = LIST_INITIALIZER (all_blocks);
+
+/* The block block assigned to each Pintos role. */
+static struct block *block_by_role[BLOCK_ROLE_CNT];
+
+static struct block *list_elem_to_block (struct list_elem *);
+
+/* Returns a human-readable name for the given block device
+   TYPE. */
+const char *
+block_type_name (enum block_type type)
+{
+  static const char *block_type_names[BLOCK_CNT] =
+    {
+      "kernel",
+      "filesys",
+      "scratch",
+      "swap",
+      "raw",
+      "foreign",
+    };
+
+  ASSERT (type < BLOCK_CNT);
+  return block_type_names[type];
+}
+
+/* Returns the block device fulfilling the given ROLE, or a null
+   pointer if no block device has been assigned that role. */
+struct block *
+block_get_role (enum block_type role)
+{
+  ASSERT (role < BLOCK_ROLE_CNT);
+  return block_by_role[role];
+}
+
+/* Assigns BLOCK the given ROLE. */
+void
+block_set_role (enum block_type role, struct block *block)
+{
+  ASSERT (role < BLOCK_ROLE_CNT);
+  block_by_role[role] = block;
+}
+
+/* Returns the first block device in kernel probe order, or a
+   null pointer if no block devices are registered. */
+struct block *
+block_first (void)
+{
+  return list_elem_to_block (list_begin (&all_blocks));
+}
+
+/* Returns the block device following BLOCK in kernel probe
+   order, or a null pointer if BLOCK is the last block device. */
+struct block *
+block_next (struct block *block)
+{
+  return list_elem_to_block (list_next (&block->list_elem));
+}
+
+/* Returns the block device with the given NAME, or a null
+   pointer if no block device has that name. */
+struct block *
+block_get_by_name (const char *name)
+{
+  struct list_elem *e;
+
+  for (e = list_begin (&all_blocks); e != list_end (&all_blocks);
+       e = list_next (e))
+    {
+      struct block *block = list_entry (e, struct block, list_elem);
+      if (!strcmp (name, block->name))
+        return block;
+    }
+
+  return NULL;
+}
+
+/* Verifies that SECTOR is a valid offset within BLOCK.
+   Panics if not. */
+static void
+check_sector (struct block *block, block_sector_t sector)
+{
+  if (sector >= block->size)
+    {
+      /* We do not use ASSERT because we want to panic here
+         regardless of whether NDEBUG is defined. */
+      PANIC ("Access past end of device %s (sector=%"PRDSNu", "
+             "size=%"PRDSNu")\n", block_name (block), sector, block->size);
+    }
+}
+
+/* Reads sector SECTOR from BLOCK into BUFFER, which must
+   have room for BLOCK_SECTOR_SIZE bytes.
+   Internally synchronizes accesses to block devices, so external
+   per-block device locking is unneeded. */
+void
+block_read (struct block *block, block_sector_t sector, void *buffer)
+{
+  check_sector (block, sector);
+  block->ops->read (block->aux, sector, buffer);
+  block->read_cnt++;
+}
+
+/* Write sector SECTOR to BLOCK from BUFFER, which must contain
+   BLOCK_SECTOR_SIZE bytes.  Returns after the block device has
+   acknowledged receiving the data.
+   Internally synchronizes accesses to block devices, so external
+   per-block device locking is unneeded. */
+void
+block_write (struct block *block, block_sector_t sector, const void *buffer)
+{
+  check_sector (block, sector);
+  ASSERT (block->type != BLOCK_FOREIGN);
+  block->ops->write (block->aux, sector, buffer);
+  block->write_cnt++;
+}
+
+/* Returns the number of sectors in BLOCK. */
+block_sector_t
+block_size (struct block *block)
+{
+  return block->size;
+}
+
+/* Returns BLOCK's name (e.g. "hda"). */
+const char *
+block_name (struct block *block)
+{
+  return block->name;
+}
+
+/* Returns BLOCK's type. */
+enum block_type
+block_type (struct block *block)
+{
+  return block->type;
+}
+
+/* Prints statistics for each block device used for a Pintos role. */
+void
+block_print_stats (void)
+{
+  int i;
+
+  for (i = 0; i < BLOCK_CNT; i++)
+    {
+      struct block *block = block_by_role[i];
+      if (block != NULL)
+        {
+          printf ("%s (%s): %llu reads, %llu writes\n",
+                  block->name, block_type_name (block->type),
+                  block->read_cnt, block->write_cnt);
+        }
+    }
+}
+
+/* Registers a new block device with the given NAME.  If
+   EXTRA_INFO is non-null, it is printed as part of a user
+   message.  The block device's SIZE in sectors and its TYPE must
+   be provided, as well as the it operation functions OPS, which
+   will be passed AUX in each function call. */
+struct block *
+block_register (const char *name, enum block_type type,
+                const char *extra_info, block_sector_t size,
+                const struct block_operations *ops, void *aux)
+{
+  struct block *block = malloc (sizeof *block);
+  if (block == NULL)
+    PANIC ("Failed to allocate memory for block device descriptor");
+
+  list_push_back (&all_blocks, &block->list_elem);
+  strlcpy (block->name, name, sizeof block->name);
+  block->type = type;
+  block->size = size;
+  block->ops = ops;
+  block->aux = aux;
+  block->read_cnt = 0;
+  block->write_cnt = 0;
+
+  printf ("%s: %'"PRDSNu" sectors (", block->name, block->size);
+  print_human_readable_size ((uint64_t) block->size * BLOCK_SECTOR_SIZE);
+  printf (")");
+  if (extra_info != NULL)
+    printf (", %s", extra_info);
+  printf ("\n");
+
+  return block;
+}
+\f
+/* Returns the block device corresponding to LIST_ELEM, or a null
+   pointer if LIST_ELEM is the list end of all_blocks. */
+static struct block *
+list_elem_to_block (struct list_elem *list_elem)
+{
+  return (list_elem != list_end (&all_blocks)
+          ? list_entry (list_elem, struct block, list_elem)
+          : NULL);
+}
+
diff --git a/src/devices/block.h b/src/devices/block.h
new file mode 100644 (file)
index 0000000..21732d6
--- /dev/null
@@ -0,0 +1,74 @@
+#ifndef DEVICES_BLOCK_H
+#define DEVICES_BLOCK_H
+
+#include <stddef.h>
+#include <inttypes.h>
+
+/* Size of a block device sector in bytes.
+   All IDE disks use this sector size, as do most USB and SCSI
+   disks.  It's not worth it to try to cater to other sector
+   sizes in Pintos (yet). */
+#define BLOCK_SECTOR_SIZE 512
+
+/* Index of a block device sector.
+   Good enough for devices up to 2 TB. */
+typedef uint32_t block_sector_t;
+
+/* Format specifier for printf(), e.g.:
+   printf ("sector=%"PRDSNu"\n", sector); */
+#define PRDSNu PRIu32
+\f
+/* Higher-level interface for file systems, etc. */
+
+struct block;
+
+/* Type of a block device. */
+enum block_type
+  {
+    /* Block device types that play a role in Pintos. */
+    BLOCK_KERNEL,                /* Pintos OS kernel. */
+    BLOCK_FILESYS,               /* File system. */
+    BLOCK_SCRATCH,               /* Scratch. */
+    BLOCK_SWAP,                  /* Swap. */
+    BLOCK_ROLE_CNT,
+
+    /* Other kinds of block devices that Pintos may see but does
+       not interact with. */
+    BLOCK_RAW = BLOCK_ROLE_CNT,  /* "Raw" device with unidentified contents. */
+    BLOCK_FOREIGN,               /* Owned by non-Pintos operating system. */
+    BLOCK_CNT                    /* Number of Pintos block types. */
+  };
+
+const char *block_type_name (enum block_type);
+
+/* Finding block devices. */
+struct block *block_get_role (enum block_type);
+void block_set_role (enum block_type, struct block *);
+struct block *block_get_by_name (const char *name);
+
+struct block *block_first (void);
+struct block *block_next (struct block *);
+
+/* Block device operations. */
+block_sector_t block_size (struct block *);
+void block_read (struct block *, block_sector_t, void *);
+void block_write (struct block *, block_sector_t, const void *);
+const char *block_name (struct block *);
+enum block_type block_type (struct block *);
+
+/* Statistics. */
+void block_print_stats (void);
+\f
+/* Lower-level interface to block device drivers. */
+
+struct block_operations
+  {
+    void (*read) (void *aux, block_sector_t, void *buffer);
+    void (*write) (void *aux, block_sector_t, const void *buffer);
+  };
+
+struct block *block_register (const char *name, enum block_type,
+                              const char *extra_info, block_sector_t size,
+                              const struct block_operations *, void *aux);
+
+#endif /* devices/block.h */
diff --git a/src/devices/disk.c b/src/devices/disk.c
deleted file mode 100644 (file)
index 14fc631..0000000
+++ /dev/null
@@ -1,571 +0,0 @@
-#include "devices/disk.h"
-#include <ctype.h>
-#include <debug.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include "devices/timer.h"
-#include "threads/io.h"
-#include "threads/interrupt.h"
-#include "threads/synch.h"
-
-/* The code in this file is an interface to an ATA (IDE)
-   controller.  It attempts to comply to [ATA-3]. */
-
-/* ATA command block port addresses. */
-#define reg_data(CHANNEL) ((CHANNEL)->reg_base + 0)     /* Data. */
-#define reg_error(CHANNEL) ((CHANNEL)->reg_base + 1)    /* Error. */
-#define reg_nsect(CHANNEL) ((CHANNEL)->reg_base + 2)    /* Sector Count. */
-#define reg_lbal(CHANNEL) ((CHANNEL)->reg_base + 3)     /* LBA 0:7. */
-#define reg_lbam(CHANNEL) ((CHANNEL)->reg_base + 4)     /* LBA 15:8. */
-#define reg_lbah(CHANNEL) ((CHANNEL)->reg_base + 5)     /* LBA 23:16. */
-#define reg_device(CHANNEL) ((CHANNEL)->reg_base + 6)   /* Device/LBA 27:24. */
-#define reg_status(CHANNEL) ((CHANNEL)->reg_base + 7)   /* Status (r/o). */
-#define reg_command(CHANNEL) reg_status (CHANNEL)       /* Command (w/o). */
-
-/* ATA control block port addresses.
-   (If we supported non-legacy ATA controllers this would not be
-   flexible enough, but it's fine for what we do.) */
-#define reg_ctl(CHANNEL) ((CHANNEL)->reg_base + 0x206)  /* Control (w/o). */
-#define reg_alt_status(CHANNEL) reg_ctl (CHANNEL)       /* Alt Status (r/o). */
-
-/* Alternate Status Register bits. */
-#define STA_BSY 0x80            /* Busy. */
-#define STA_DRDY 0x40           /* Device Ready. */
-#define STA_DRQ 0x08            /* Data Request. */
-
-/* Control Register bits. */
-#define CTL_SRST 0x04           /* Software Reset. */
-
-/* Device Register bits. */
-#define DEV_MBS 0xa0            /* Must be set. */
-#define DEV_LBA 0x40            /* Linear based addressing. */
-#define DEV_DEV 0x10            /* Select device: 0=master, 1=slave. */
-
-/* Commands.
-   Many more are defined but this is the small subset that we
-   use. */
-#define CMD_IDENTIFY_DEVICE 0xec        /* IDENTIFY DEVICE. */
-#define CMD_READ_SECTOR_RETRY 0x20      /* READ SECTOR with retries. */
-#define CMD_WRITE_SECTOR_RETRY 0x30     /* WRITE SECTOR with retries. */
-
-/* An ATA device. */
-struct disk 
-  {
-    char name[8];               /* Name, e.g. "hd0:1". */
-    struct channel *channel;    /* Channel disk is on. */
-    int dev_no;                 /* Device 0 or 1 for master or slave. */
-
-    bool is_ata;                /* 1=This device is an ATA disk. */
-    disk_sector_t capacity;     /* Capacity in sectors (if is_ata). */
-
-    long long read_cnt;         /* Number of sectors read. */
-    long long write_cnt;        /* Number of sectors written. */
-  };
-
-/* An ATA channel (aka controller).
-   Each channel can control up to two disks. */
-struct channel 
-  {
-    char name[8];               /* Name, e.g. "hd0". */
-    uint16_t reg_base;          /* Base I/O port. */
-    uint8_t irq;                /* Interrupt in use. */
-
-    struct lock lock;           /* Must acquire to access the controller. */
-    bool expecting_interrupt;   /* True if an interrupt is expected, false if
-                                   any interrupt would be spurious. */
-    struct semaphore completion_wait;   /* Up'd by interrupt handler. */
-
-    struct disk devices[2];     /* The devices on this channel. */
-  };
-
-/* We support the two "legacy" ATA channels found in a standard PC. */
-#define CHANNEL_CNT 2
-static struct channel channels[CHANNEL_CNT];
-
-static void reset_channel (struct channel *);
-static bool check_device_type (struct disk *);
-static void identify_ata_device (struct disk *);
-
-static void select_sector (struct disk *, disk_sector_t);
-static void issue_pio_command (struct channel *, uint8_t command);
-static void input_sector (struct channel *, void *);
-static void output_sector (struct channel *, const void *);
-
-static void wait_until_idle (const struct disk *);
-static bool wait_while_busy (const struct disk *);
-static void select_device (const struct disk *);
-static void select_device_wait (const struct disk *);
-
-static void interrupt_handler (struct intr_frame *);
-
-/* Initialize the disk subsystem and detect disks. */
-void
-disk_init (void) 
-{
-  size_t chan_no;
-
-  for (chan_no = 0; chan_no < CHANNEL_CNT; chan_no++)
-    {
-      struct channel *c = &channels[chan_no];
-      int dev_no;
-
-      /* Initialize channel. */
-      snprintf (c->name, sizeof c->name, "hd%zu", chan_no);
-      switch (chan_no) 
-        {
-        case 0:
-          c->reg_base = 0x1f0;
-          c->irq = 14 + 0x20;
-          break;
-        case 1:
-          c->reg_base = 0x170;
-          c->irq = 15 + 0x20;
-          break;
-        default:
-          NOT_REACHED ();
-        }
-      lock_init (&c->lock);
-      c->expecting_interrupt = false;
-      sema_init (&c->completion_wait, 0);
-      /* Initialize devices. */
-      for (dev_no = 0; dev_no < 2; dev_no++)
-        {
-          struct disk *d = &c->devices[dev_no];
-          snprintf (d->name, sizeof d->name, "%s:%d", c->name, dev_no);
-          d->channel = c;
-          d->dev_no = dev_no;
-
-          d->is_ata = false;
-          d->capacity = 0;
-
-          d->read_cnt = d->write_cnt = 0;
-        }
-
-      /* Register interrupt handler. */
-      intr_register_ext (c->irq, interrupt_handler, c->name);
-
-      /* Reset hardware. */
-      reset_channel (c);
-
-      /* Distinguish ATA hard disks from other devices. */
-      if (check_device_type (&c->devices[0]))
-        check_device_type (&c->devices[1]);
-
-      /* Read hard disk identity information. */
-      for (dev_no = 0; dev_no < 2; dev_no++)
-        if (c->devices[dev_no].is_ata)
-          identify_ata_device (&c->devices[dev_no]);
-    }
-}
-
-/* Prints disk statistics. */
-void
-disk_print_stats (void) 
-{
-  int chan_no;
-
-  for (chan_no = 0; chan_no < CHANNEL_CNT; chan_no++) 
-    {
-      int dev_no;
-
-      for (dev_no = 0; dev_no < 2; dev_no++) 
-        {
-          struct disk *d = disk_get (chan_no, dev_no);
-          if (d != NULL && d->is_ata) 
-            printf ("%s: %lld reads, %lld writes\n",
-                    d->name, d->read_cnt, d->write_cnt);
-        }
-    }
-}
-
-/* Returns the disk numbered DEV_NO--either 0 or 1 for master or
-   slave, respectively--within the channel numbered CHAN_NO.
-
-   Pintos uses disks this way:
-        0:0 - boot loader, command line args, and operating system kernel
-        0:1 - file system
-        1:0 - scratch
-        1:1 - swap
-*/
-struct disk *
-disk_get (int chan_no, int dev_no) 
-{
-  ASSERT (dev_no == 0 || dev_no == 1);
-
-  if (chan_no < (int) CHANNEL_CNT) 
-    {
-      struct disk *d = &channels[chan_no].devices[dev_no];
-      if (d->is_ata)
-        return d; 
-    }
-  return NULL;
-}
-
-/* Returns the size of disk D, measured in DISK_SECTOR_SIZE-byte
-   sectors. */
-disk_sector_t
-disk_size (struct disk *d) 
-{
-  ASSERT (d != NULL);
-  
-  return d->capacity;
-}
-
-/* Reads sector SEC_NO from disk D into BUFFER, which must have
-   room for DISK_SECTOR_SIZE bytes.
-   Internally synchronizes accesses to disks, so external
-   per-disk locking is unneeded. */
-void
-disk_read (struct disk *d, disk_sector_t sec_no, void *buffer) 
-{
-  struct channel *c;
-  
-  ASSERT (d != NULL);
-  ASSERT (buffer != NULL);
-
-  c = d->channel;
-  lock_acquire (&c->lock);
-  select_sector (d, sec_no);
-  issue_pio_command (c, CMD_READ_SECTOR_RETRY);
-  sema_down (&c->completion_wait);
-  if (!wait_while_busy (d))
-    PANIC ("%s: disk read failed, sector=%"PRDSNu, d->name, sec_no);
-  input_sector (c, buffer);
-  d->read_cnt++;
-  lock_release (&c->lock);
-}
-
-/* Write sector SEC_NO to disk D from BUFFER, which must contain
-   DISK_SECTOR_SIZE bytes.  Returns after the disk has
-   acknowledged receiving the data.
-   Internally synchronizes accesses to disks, so external
-   per-disk locking is unneeded. */
-void
-disk_write (struct disk *d, disk_sector_t sec_no, const void *buffer)
-{
-  struct channel *c;
-  
-  ASSERT (d != NULL);
-  ASSERT (buffer != NULL);
-
-  c = d->channel;
-  lock_acquire (&c->lock);
-  select_sector (d, sec_no);
-  issue_pio_command (c, CMD_WRITE_SECTOR_RETRY);
-  if (!wait_while_busy (d))
-    PANIC ("%s: disk write failed, sector=%"PRDSNu, d->name, sec_no);
-  output_sector (c, buffer);
-  sema_down (&c->completion_wait);
-  d->write_cnt++;
-  lock_release (&c->lock);
-}
-\f
-/* Disk detection and identification. */
-
-static void print_ata_string (char *string, size_t size);
-
-/* Resets an ATA channel and waits for any devices present on it
-   to finish the reset. */
-static void
-reset_channel (struct channel *c) 
-{
-  bool present[2];
-  int dev_no;
-
-  /* The ATA reset sequence depends on which devices are present,
-     so we start by detecting device presence. */
-  for (dev_no = 0; dev_no < 2; dev_no++)
-    {
-      struct disk *d = &c->devices[dev_no];
-
-      select_device (d);
-
-      outb (reg_nsect (c), 0x55);
-      outb (reg_lbal (c), 0xaa);
-
-      outb (reg_nsect (c), 0xaa);
-      outb (reg_lbal (c), 0x55);
-
-      outb (reg_nsect (c), 0x55);
-      outb (reg_lbal (c), 0xaa);
-
-      present[dev_no] = (inb (reg_nsect (c)) == 0x55
-                         && inb (reg_lbal (c)) == 0xaa);
-    }
-
-  /* Issue soft reset sequence, which selects device 0 as a side effect.
-     Also enable interrupts. */
-  outb (reg_ctl (c), 0);
-  timer_usleep (10);
-  outb (reg_ctl (c), CTL_SRST);
-  timer_usleep (10);
-  outb (reg_ctl (c), 0);
-
-  timer_msleep (150);
-
-  /* Wait for device 0 to clear BSY. */
-  if (present[0]) 
-    {
-      select_device (&c->devices[0]);
-      wait_while_busy (&c->devices[0]); 
-    }
-
-  /* Wait for device 1 to clear BSY. */
-  if (present[1])
-    {
-      int i;
-
-      select_device (&c->devices[1]);
-      for (i = 0; i < 3000; i++) 
-        {
-          if (inb (reg_nsect (c)) == 1 && inb (reg_lbal (c)) == 1)
-            break;
-          timer_msleep (10);
-        }
-      wait_while_busy (&c->devices[1]);
-    }
-}
-
-/* Checks whether device D is an ATA disk and sets D's is_ata
-   member appropriately.  If D is device 0 (master), returns true
-   if it's possible that a slave (device 1) exists on this
-   channel.  If D is device 1 (slave), the return value is not
-   meaningful. */
-static bool
-check_device_type (struct disk *d) 
-{
-  struct channel *c = d->channel;
-  uint8_t error, lbam, lbah, status;
-
-  select_device (d);
-
-  error = inb (reg_error (c));
-  lbam = inb (reg_lbam (c));
-  lbah = inb (reg_lbah (c));
-  status = inb (reg_status (c));
-
-  if ((error != 1 && (error != 0x81 || d->dev_no == 1))
-      || (status & STA_DRDY) == 0
-      || (status & STA_BSY) != 0)
-    {
-      d->is_ata = false;
-      return error != 0x81;      
-    }
-  else 
-    {
-      d->is_ata = (lbam == 0 && lbah == 0) || (lbam == 0x3c && lbah == 0xc3);
-      return true; 
-    }
-}
-
-/* Sends an IDENTIFY DEVICE command to disk D and reads the
-   response.  Initializes D's capacity member based on the result
-   and prints a message describing the disk to the console. */
-static void
-identify_ata_device (struct disk *d) 
-{
-  struct channel *c = d->channel;
-  uint16_t id[DISK_SECTOR_SIZE / 2];
-
-  ASSERT (d->is_ata);
-
-  /* Send the IDENTIFY DEVICE command, wait for an interrupt
-     indicating the device's response is ready, and read the data
-     into our buffer. */
-  select_device_wait (d);
-  issue_pio_command (c, CMD_IDENTIFY_DEVICE);
-  sema_down (&c->completion_wait);
-  if (!wait_while_busy (d))
-    {
-      d->is_ata = false;
-      return;
-    }
-  input_sector (c, id);
-
-  /* Calculate capacity. */
-  d->capacity = id[60] | ((uint32_t) id[61] << 16);
-
-  /* Print identification message. */
-  printf ("%s: detected %'"PRDSNu" sector (", d->name, d->capacity);
-  if (d->capacity > 1024 / DISK_SECTOR_SIZE * 1024 * 1024)
-    printf ("%"PRDSNu" GB",
-            d->capacity / (1024 / DISK_SECTOR_SIZE * 1024 * 1024));
-  else if (d->capacity > 1024 / DISK_SECTOR_SIZE * 1024)
-    printf ("%"PRDSNu" MB", d->capacity / (1024 / DISK_SECTOR_SIZE * 1024));
-  else if (d->capacity > 1024 / DISK_SECTOR_SIZE)
-    printf ("%"PRDSNu" kB", d->capacity / (1024 / DISK_SECTOR_SIZE));
-  else
-    printf ("%"PRDSNu" byte", d->capacity * DISK_SECTOR_SIZE);
-  printf (") disk, model \"");
-  print_ata_string ((char *) &id[27], 40);
-  printf ("\", serial \"");
-  print_ata_string ((char *) &id[10], 20);
-  printf ("\"\n");
-}
-
-/* Prints STRING, which consists of SIZE bytes in a funky format:
-   each pair of bytes is in reverse order.  Does not print
-   trailing whitespace and/or nulls. */
-static void
-print_ata_string (char *string, size_t size) 
-{
-  size_t i;
-
-  /* Find the last non-white, non-null character. */
-  for (; size > 0; size--)
-    {
-      int c = string[(size - 1) ^ 1];
-      if (c != '\0' && !isspace (c))
-        break; 
-    }
-
-  /* Print. */
-  for (i = 0; i < size; i++)
-    printf ("%c", string[i ^ 1]);
-}
-\f
-/* Selects device D, waiting for it to become ready, and then
-   writes SEC_NO to the disk's sector selection registers.  (We
-   use LBA mode.) */
-static void
-select_sector (struct disk *d, disk_sector_t sec_no) 
-{
-  struct channel *c = d->channel;
-
-  ASSERT (sec_no < d->capacity);
-  ASSERT (sec_no < (1UL << 28));
-  
-  select_device_wait (d);
-  outb (reg_nsect (c), 1);
-  outb (reg_lbal (c), sec_no);
-  outb (reg_lbam (c), sec_no >> 8);
-  outb (reg_lbah (c), (sec_no >> 16));
-  outb (reg_device (c),
-        DEV_MBS | DEV_LBA | (d->dev_no == 1 ? DEV_DEV : 0) | (sec_no >> 24));
-}
-
-/* Writes COMMAND to channel C and prepares for receiving a
-   completion interrupt. */
-static void
-issue_pio_command (struct channel *c, uint8_t command) 
-{
-  /* Interrupts must be enabled or our semaphore will never be
-     up'd by the completion handler. */
-  ASSERT (intr_get_level () == INTR_ON);
-
-  c->expecting_interrupt = true;
-  outb (reg_command (c), command);
-}
-
-/* Reads a sector from channel C's data register in PIO mode into
-   SECTOR, which must have room for DISK_SECTOR_SIZE bytes. */
-static void
-input_sector (struct channel *c, void *sector) 
-{
-  insw (reg_data (c), sector, DISK_SECTOR_SIZE / 2);
-}
-
-/* Writes SECTOR to channel C's data register in PIO mode.
-   SECTOR must contain DISK_SECTOR_SIZE bytes. */
-static void
-output_sector (struct channel *c, const void *sector) 
-{
-  outsw (reg_data (c), sector, DISK_SECTOR_SIZE / 2);
-}
-\f
-/* Low-level ATA primitives. */
-
-/* Wait up to 10 seconds for the controller to become idle, that
-   is, for the BSY and DRQ bits to clear in the status register.
-
-   As a side effect, reading the status register clears any
-   pending interrupt. */
-static void
-wait_until_idle (const struct disk *d) 
-{
-  int i;
-
-  for (i = 0; i < 1000; i++) 
-    {
-      if ((inb (reg_status (d->channel)) & (STA_BSY | STA_DRQ)) == 0)
-        return;
-      timer_usleep (10);
-    }
-
-  printf ("%s: idle timeout\n", d->name);
-}
-
-/* Wait up to 30 seconds for disk D to clear BSY,
-   and then return the status of the DRQ bit.
-   The ATA standards say that a disk may take as long as that to
-   complete its reset. */
-static bool
-wait_while_busy (const struct disk *d) 
-{
-  struct channel *c = d->channel;
-  int i;
-  
-  for (i = 0; i < 3000; i++)
-    {
-      if (i == 700)
-        printf ("%s: busy, waiting...", d->name);
-      if (!(inb (reg_alt_status (c)) & STA_BSY)) 
-        {
-          if (i >= 700)
-            printf ("ok\n");
-          return (inb (reg_alt_status (c)) & STA_DRQ) != 0;
-        }
-      timer_msleep (10);
-    }
-
-  printf ("failed\n");
-  return false;
-}
-
-/* Program D's channel so that D is now the selected disk. */
-static void
-select_device (const struct disk *d)
-{
-  struct channel *c = d->channel;
-  uint8_t dev = DEV_MBS;
-  if (d->dev_no == 1)
-    dev |= DEV_DEV;
-  outb (reg_device (c), dev);
-  inb (reg_alt_status (c));
-  timer_nsleep (400);
-}
-
-/* Select disk D in its channel, as select_device(), but wait for
-   the channel to become idle before and after. */
-static void
-select_device_wait (const struct disk *d) 
-{
-  wait_until_idle (d);
-  select_device (d);
-  wait_until_idle (d);
-}
-\f
-/* ATA interrupt handler. */
-static void
-interrupt_handler (struct intr_frame *f) 
-{
-  struct channel *c;
-
-  for (c = channels; c < channels + CHANNEL_CNT; c++)
-    if (f->vec_no == c->irq)
-      {
-        if (c->expecting_interrupt) 
-          {
-            inb (reg_status (c));               /* Acknowledge interrupt. */
-            sema_up (&c->completion_wait);      /* Wake up waiter. */
-          }
-        else
-          printf ("%s: unexpected interrupt\n", c->name);
-        return;
-      }
-
-  NOT_REACHED ();
-}
-
-
diff --git a/src/devices/disk.h b/src/devices/disk.h
deleted file mode 100644 (file)
index 3bcbb9a..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-#ifndef DEVICES_DISK_H
-#define DEVICES_DISK_H
-
-#include <inttypes.h>
-#include <stdint.h>
-
-/* Size of a disk sector in bytes. */
-#define DISK_SECTOR_SIZE 512
-
-/* Index of a disk sector within a disk.
-   Good enough for disks up to 2 TB. */
-typedef uint32_t disk_sector_t;
-
-/* Format specifier for printf(), e.g.:
-   printf ("sector=%"PRDSNu"\n", sector); */
-#define PRDSNu PRIu32
-
-void disk_init (void);
-void disk_print_stats (void);
-
-struct disk *disk_get (int chan_no, int dev_no);
-disk_sector_t disk_size (struct disk *);
-void disk_read (struct disk *, disk_sector_t, void *);
-void disk_write (struct disk *, disk_sector_t, const void *);
-
-#endif /* devices/disk.h */
diff --git a/src/devices/ide.c b/src/devices/ide.c
new file mode 100644 (file)
index 0000000..2cc0292
--- /dev/null
@@ -0,0 +1,527 @@
+#include "devices/ide.h"
+#include <ctype.h>
+#include <debug.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include "devices/block.h"
+#include "devices/partition.h"
+#include "devices/timer.h"
+#include "threads/io.h"
+#include "threads/interrupt.h"
+#include "threads/synch.h"
+
+/* The code in this file is an interface to an ATA (IDE)
+   controller.  It attempts to comply to [ATA-3]. */
+
+/* ATA command block port addresses. */
+#define reg_data(CHANNEL) ((CHANNEL)->reg_base + 0)     /* Data. */
+#define reg_error(CHANNEL) ((CHANNEL)->reg_base + 1)    /* Error. */
+#define reg_nsect(CHANNEL) ((CHANNEL)->reg_base + 2)    /* Sector Count. */
+#define reg_lbal(CHANNEL) ((CHANNEL)->reg_base + 3)     /* LBA 0:7. */
+#define reg_lbam(CHANNEL) ((CHANNEL)->reg_base + 4)     /* LBA 15:8. */
+#define reg_lbah(CHANNEL) ((CHANNEL)->reg_base + 5)     /* LBA 23:16. */
+#define reg_device(CHANNEL) ((CHANNEL)->reg_base + 6)   /* Device/LBA 27:24. */
+#define reg_status(CHANNEL) ((CHANNEL)->reg_base + 7)   /* Status (r/o). */
+#define reg_command(CHANNEL) reg_status (CHANNEL)       /* Command (w/o). */
+
+/* ATA control block port addresses.
+   (If we supported non-legacy ATA controllers this would not be
+   flexible enough, but it's fine for what we do.) */
+#define reg_ctl(CHANNEL) ((CHANNEL)->reg_base + 0x206)  /* Control (w/o). */
+#define reg_alt_status(CHANNEL) reg_ctl (CHANNEL)       /* Alt Status (r/o). */
+
+/* Alternate Status Register bits. */
+#define STA_BSY 0x80            /* Busy. */
+#define STA_DRDY 0x40           /* Device Ready. */
+#define STA_DRQ 0x08            /* Data Request. */
+
+/* Control Register bits. */
+#define CTL_SRST 0x04           /* Software Reset. */
+
+/* Device Register bits. */
+#define DEV_MBS 0xa0            /* Must be set. */
+#define DEV_LBA 0x40            /* Linear based addressing. */
+#define DEV_DEV 0x10            /* Select device: 0=master, 1=slave. */
+
+/* Commands.
+   Many more are defined but this is the small subset that we
+   use. */
+#define CMD_IDENTIFY_DEVICE 0xec        /* IDENTIFY DEVICE. */
+#define CMD_READ_SECTOR_RETRY 0x20      /* READ SECTOR with retries. */
+#define CMD_WRITE_SECTOR_RETRY 0x30     /* WRITE SECTOR with retries. */
+
+/* An ATA device. */
+struct ata_disk
+  {
+    char name[8];               /* Name, e.g. "hda". */
+    struct channel *channel;    /* Channel that disk is attached to. */
+    int dev_no;                 /* Device 0 or 1 for master or slave. */
+    bool is_ata;                /* Is device an ATA disk? */
+  };
+
+/* An ATA channel (aka controller).
+   Each channel can control up to two disks. */
+struct channel
+  {
+    char name[8];               /* Name, e.g. "ide0". */
+    uint16_t reg_base;          /* Base I/O port. */
+    uint8_t irq;                /* Interrupt in use. */
+
+    struct lock lock;           /* Must acquire to access the controller. */
+    bool expecting_interrupt;   /* True if an interrupt is expected, false if
+                                   any interrupt would be spurious. */
+    struct semaphore completion_wait;   /* Up'd by interrupt handler. */
+
+    struct ata_disk devices[2];     /* The devices on this channel. */
+  };
+
+/* We support the two "legacy" ATA channels found in a standard PC. */
+#define CHANNEL_CNT 2
+static struct channel channels[CHANNEL_CNT];
+
+static struct block_operations ide_operations;
+
+static void reset_channel (struct channel *);
+static bool check_device_type (struct ata_disk *);
+static void identify_ata_device (struct ata_disk *);
+
+static void select_sector (struct ata_disk *, block_sector_t);
+static void issue_pio_command (struct channel *, uint8_t command);
+static void input_sector (struct channel *, void *);
+static void output_sector (struct channel *, const void *);
+
+static void wait_until_idle (const struct ata_disk *);
+static bool wait_while_busy (const struct ata_disk *);
+static void select_device (const struct ata_disk *);
+static void select_device_wait (const struct ata_disk *);
+
+static void interrupt_handler (struct intr_frame *);
+
+/* Initialize the disk subsystem and detect disks. */
+void
+ide_init (void) 
+{
+  size_t chan_no;
+
+  for (chan_no = 0; chan_no < CHANNEL_CNT; chan_no++)
+    {
+      struct channel *c = &channels[chan_no];
+      int dev_no;
+
+      /* Initialize channel. */
+      snprintf (c->name, sizeof c->name, "ide%zu", chan_no);
+      switch (chan_no) 
+        {
+        case 0:
+          c->reg_base = 0x1f0;
+          c->irq = 14 + 0x20;
+          break;
+        case 1:
+          c->reg_base = 0x170;
+          c->irq = 15 + 0x20;
+          break;
+        default:
+          NOT_REACHED ();
+        }
+      lock_init (&c->lock);
+      c->expecting_interrupt = false;
+      sema_init (&c->completion_wait, 0);
+      /* Initialize devices. */
+      for (dev_no = 0; dev_no < 2; dev_no++)
+        {
+          struct ata_disk *d = &c->devices[dev_no];
+          snprintf (d->name, sizeof d->name,
+                    "hd%c", 'a' + chan_no * 2 + dev_no); 
+          d->channel = c;
+          d->dev_no = dev_no;
+          d->is_ata = false;
+        }
+
+      /* Register interrupt handler. */
+      intr_register_ext (c->irq, interrupt_handler, c->name);
+
+      /* Reset hardware. */
+      reset_channel (c);
+
+      /* Distinguish ATA hard disks from other devices. */
+      if (check_device_type (&c->devices[0]))
+        check_device_type (&c->devices[1]);
+
+      /* Read hard disk identity information. */
+      for (dev_no = 0; dev_no < 2; dev_no++)
+        if (c->devices[dev_no].is_ata)
+          identify_ata_device (&c->devices[dev_no]);
+    }
+}
+\f
+/* Disk detection and identification. */
+
+static char *descramble_ata_string (char *, int size);
+
+/* Resets an ATA channel and waits for any devices present on it
+   to finish the reset. */
+static void
+reset_channel (struct channel *c) 
+{
+  bool present[2];
+  int dev_no;
+
+  /* The ATA reset sequence depends on which devices are present,
+     so we start by detecting device presence. */
+  for (dev_no = 0; dev_no < 2; dev_no++)
+    {
+      struct ata_disk *d = &c->devices[dev_no];
+
+      select_device (d);
+
+      outb (reg_nsect (c), 0x55);
+      outb (reg_lbal (c), 0xaa);
+
+      outb (reg_nsect (c), 0xaa);
+      outb (reg_lbal (c), 0x55);
+
+      outb (reg_nsect (c), 0x55);
+      outb (reg_lbal (c), 0xaa);
+
+      present[dev_no] = (inb (reg_nsect (c)) == 0x55
+                         && inb (reg_lbal (c)) == 0xaa);
+    }
+
+  /* Issue soft reset sequence, which selects device 0 as a side effect.
+     Also enable interrupts. */
+  outb (reg_ctl (c), 0);
+  timer_usleep (10);
+  outb (reg_ctl (c), CTL_SRST);
+  timer_usleep (10);
+  outb (reg_ctl (c), 0);
+
+  timer_msleep (150);
+
+  /* Wait for device 0 to clear BSY. */
+  if (present[0]) 
+    {
+      select_device (&c->devices[0]);
+      wait_while_busy (&c->devices[0]); 
+    }
+
+  /* Wait for device 1 to clear BSY. */
+  if (present[1])
+    {
+      int i;
+
+      select_device (&c->devices[1]);
+      for (i = 0; i < 3000; i++) 
+        {
+          if (inb (reg_nsect (c)) == 1 && inb (reg_lbal (c)) == 1)
+            break;
+          timer_msleep (10);
+        }
+      wait_while_busy (&c->devices[1]);
+    }
+}
+
+/* Checks whether device D is an ATA disk and sets D's is_ata
+   member appropriately.  If D is device 0 (master), returns true
+   if it's possible that a slave (device 1) exists on this
+   channel.  If D is device 1 (slave), the return value is not
+   meaningful. */
+static bool
+check_device_type (struct ata_disk *d) 
+{
+  struct channel *c = d->channel;
+  uint8_t error, lbam, lbah, status;
+
+  select_device (d);
+
+  error = inb (reg_error (c));
+  lbam = inb (reg_lbam (c));
+  lbah = inb (reg_lbah (c));
+  status = inb (reg_status (c));
+
+  if ((error != 1 && (error != 0x81 || d->dev_no == 1))
+      || (status & STA_DRDY) == 0
+      || (status & STA_BSY) != 0)
+    {
+      d->is_ata = false;
+      return error != 0x81;      
+    }
+  else 
+    {
+      d->is_ata = (lbam == 0 && lbah == 0) || (lbam == 0x3c && lbah == 0xc3);
+      return true; 
+    }
+}
+
+/* Sends an IDENTIFY DEVICE command to disk D and reads the
+   response.  Registers the disk with the block device
+   layer. */
+static void
+identify_ata_device (struct ata_disk *d) 
+{
+  struct channel *c = d->channel;
+  char id[BLOCK_SECTOR_SIZE];
+  block_sector_t capacity;
+  char *model, *serial;
+  char extra_info[128];
+  struct block *block;
+
+  ASSERT (d->is_ata);
+
+  /* Send the IDENTIFY DEVICE command, wait for an interrupt
+     indicating the device's response is ready, and read the data
+     into our buffer. */
+  select_device_wait (d);
+  issue_pio_command (c, CMD_IDENTIFY_DEVICE);
+  sema_down (&c->completion_wait);
+  if (!wait_while_busy (d))
+    {
+      d->is_ata = false;
+      return;
+    }
+  input_sector (c, id);
+
+  /* Calculate capacity.
+     Read model name and serial number. */
+  capacity = *(uint32_t *) &id[60 * 2];
+  model = descramble_ata_string (&id[10 * 2], 20);
+  serial = descramble_ata_string (&id[27 * 2], 40);
+  snprintf (extra_info, sizeof extra_info,
+            "model \"%s\", serial \"%s\"", model, serial);
+
+  /* Disable access to IDE disks over 1 GB, which are likely
+     physical IDE disks rather than virtual ones.  If we don't
+     allow access to those, we're less likely to scribble on
+     someone's important data.  You can disable this check by
+     hand if you really want to do so. */
+  if (capacity >= 1024 * 1024 * 1024 / BLOCK_SECTOR_SIZE)
+    {
+      printf ("%s: ignoring ", d->name);
+      print_human_readable_size (capacity * 512);
+      printf ("disk for safety\n");
+      d->is_ata = false;
+      return;
+    }
+
+  /* Register. */
+  block = block_register (d->name, BLOCK_RAW, extra_info, capacity,
+                          &ide_operations, d);
+  partition_scan (block);
+}
+
+/* Translates STRING, which consists of SIZE bytes in a funky
+   format, into a null-terminated string in-place.  Drops
+   trailing whitespace and null bytes.  Returns STRING.  */
+static char *
+descramble_ata_string (char *string, int size) 
+{
+  int i;
+
+  /* Swap all pairs of bytes. */
+  for (i = 0; i + 1 < size; i += 2)
+    {
+      char tmp = string[i];
+      string[i] = string[i + 1];
+      string[i + 1] = tmp;
+    }
+
+  /* Find the last non-white, non-null character. */
+  for (size--; size > 0; size--)
+    {
+      int c = string[size - 1];
+      if (c != '\0' && !isspace (c))
+        break; 
+    }
+  string[size] = '\0';
+
+  return string;
+}
+\f
+/* Reads sector SEC_NO from disk D into BUFFER, which must have
+   room for BLOCK_SECTOR_SIZE bytes.
+   Internally synchronizes accesses to disks, so external
+   per-disk locking is unneeded. */
+static void
+ide_read (void *d_, block_sector_t sec_no, void *buffer)
+{
+  struct ata_disk *d = d_;
+  struct channel *c = d->channel;
+  lock_acquire (&c->lock);
+  select_sector (d, sec_no);
+  issue_pio_command (c, CMD_READ_SECTOR_RETRY);
+  sema_down (&c->completion_wait);
+  if (!wait_while_busy (d))
+    PANIC ("%s: disk read failed, sector=%"PRDSNu, d->name, sec_no);
+  input_sector (c, buffer);
+  lock_release (&c->lock);
+}
+
+/* Write sector SEC_NO to disk D from BUFFER, which must contain
+   BLOCK_SECTOR_SIZE bytes.  Returns after the disk has
+   acknowledged receiving the data.
+   Internally synchronizes accesses to disks, so external
+   per-disk locking is unneeded. */
+static void
+ide_write (void *d_, block_sector_t sec_no, const void *buffer)
+{
+  struct ata_disk *d = d_;
+  struct channel *c = d->channel;
+  lock_acquire (&c->lock);
+  select_sector (d, sec_no);
+  issue_pio_command (c, CMD_WRITE_SECTOR_RETRY);
+  if (!wait_while_busy (d))
+    PANIC ("%s: disk write failed, sector=%"PRDSNu, d->name, sec_no);
+  output_sector (c, buffer);
+  sema_down (&c->completion_wait);
+  lock_release (&c->lock);
+}
+
+static struct block_operations ide_operations =
+  {
+    ide_read,
+    ide_write
+  };
+\f
+/* Selects device D, waiting for it to become ready, and then
+   writes SEC_NO to the disk's sector selection registers.  (We
+   use LBA mode.) */
+static void
+select_sector (struct ata_disk *d, block_sector_t sec_no)
+{
+  struct channel *c = d->channel;
+
+  ASSERT (sec_no < (1UL << 28));
+  
+  select_device_wait (d);
+  outb (reg_nsect (c), 1);
+  outb (reg_lbal (c), sec_no);
+  outb (reg_lbam (c), sec_no >> 8);
+  outb (reg_lbah (c), (sec_no >> 16));
+  outb (reg_device (c),
+        DEV_MBS | DEV_LBA | (d->dev_no == 1 ? DEV_DEV : 0) | (sec_no >> 24));
+}
+
+/* Writes COMMAND to channel C and prepares for receiving a
+   completion interrupt. */
+static void
+issue_pio_command (struct channel *c, uint8_t command) 
+{
+  /* Interrupts must be enabled or our semaphore will never be
+     up'd by the completion handler. */
+  ASSERT (intr_get_level () == INTR_ON);
+
+  c->expecting_interrupt = true;
+  outb (reg_command (c), command);
+}
+
+/* Reads a sector from channel C's data register in PIO mode into
+   SECTOR, which must have room for BLOCK_SECTOR_SIZE bytes. */
+static void
+input_sector (struct channel *c, void *sector) 
+{
+  insw (reg_data (c), sector, BLOCK_SECTOR_SIZE / 2);
+}
+
+/* Writes SECTOR to channel C's data register in PIO mode.
+   SECTOR must contain BLOCK_SECTOR_SIZE bytes. */
+static void
+output_sector (struct channel *c, const void *sector) 
+{
+  outsw (reg_data (c), sector, BLOCK_SECTOR_SIZE / 2);
+}
+\f
+/* Low-level ATA primitives. */
+
+/* Wait up to 10 seconds for the controller to become idle, that
+   is, for the BSY and DRQ bits to clear in the status register.
+
+   As a side effect, reading the status register clears any
+   pending interrupt. */
+static void
+wait_until_idle (const struct ata_disk *d) 
+{
+  int i;
+
+  for (i = 0; i < 1000; i++) 
+    {
+      if ((inb (reg_status (d->channel)) & (STA_BSY | STA_DRQ)) == 0)
+        return;
+      timer_usleep (10);
+    }
+
+  printf ("%s: idle timeout\n", d->name);
+}
+
+/* Wait up to 30 seconds for disk D to clear BSY,
+   and then return the status of the DRQ bit.
+   The ATA standards say that a disk may take as long as that to
+   complete its reset. */
+static bool
+wait_while_busy (const struct ata_disk *d) 
+{
+  struct channel *c = d->channel;
+  int i;
+  
+  for (i = 0; i < 3000; i++)
+    {
+      if (i == 700)
+        printf ("%s: busy, waiting...", d->name);
+      if (!(inb (reg_alt_status (c)) & STA_BSY)) 
+        {
+          if (i >= 700)
+            printf ("ok\n");
+          return (inb (reg_alt_status (c)) & STA_DRQ) != 0;
+        }
+      timer_msleep (10);
+    }
+
+  printf ("failed\n");
+  return false;
+}
+
+/* Program D's channel so that D is now the selected disk. */
+static void
+select_device (const struct ata_disk *d)
+{
+  struct channel *c = d->channel;
+  uint8_t dev = DEV_MBS;
+  if (d->dev_no == 1)
+    dev |= DEV_DEV;
+  outb (reg_device (c), dev);
+  inb (reg_alt_status (c));
+  timer_nsleep (400);
+}
+
+/* Select disk D in its channel, as select_device(), but wait for
+   the channel to become idle before and after. */
+static void
+select_device_wait (const struct ata_disk *d) 
+{
+  wait_until_idle (d);
+  select_device (d);
+  wait_until_idle (d);
+}
+\f
+/* ATA interrupt handler. */
+static void
+interrupt_handler (struct intr_frame *f) 
+{
+  struct channel *c;
+
+  for (c = channels; c < channels + CHANNEL_CNT; c++)
+    if (f->vec_no == c->irq)
+      {
+        if (c->expecting_interrupt) 
+          {
+            inb (reg_status (c));               /* Acknowledge interrupt. */
+            sema_up (&c->completion_wait);      /* Wake up waiter. */
+          }
+        else
+          printf ("%s: unexpected interrupt\n", c->name);
+        return;
+      }
+
+  NOT_REACHED ();
+}
+
+
diff --git a/src/devices/ide.h b/src/devices/ide.h
new file mode 100644 (file)
index 0000000..b35da5e
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef DEVICES_IDE_H
+#define DEVICES_IDE_H
+
+void ide_init (void);
+
+#endif /* devices/ide.h */
diff --git a/src/devices/partition.c b/src/devices/partition.c
new file mode 100644 (file)
index 0000000..7e97332
--- /dev/null
@@ -0,0 +1,324 @@
+#include "devices/partition.h"
+#include <packed.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include "devices/block.h"
+#include "threads/malloc.h"
+
+/* A partition of a block device. */
+struct partition
+  {
+    struct block *block;                /* Underlying block device. */
+    block_sector_t start;               /* First sector within device. */
+  };
+
+static struct block_operations partition_operations;
+
+static void read_partition_table (struct block *, block_sector_t sector,
+                                  block_sector_t primary_extended_sector,
+                                  int *part_nr);
+static void found_partition (struct block *, uint8_t type,
+                             block_sector_t start, block_sector_t size,
+                             int part_nr);
+static const char *partition_type_name (uint8_t);
+
+/* Scans BLOCK for partitions of interest to Pintos. */
+void
+partition_scan (struct block *block)
+{
+  int part_nr = 0;
+  read_partition_table (block, 0, 0, &part_nr);
+  if (part_nr == 0)
+    printf ("%s: Device contains no partitions\n", block_name (block));
+}
+
+/* Reads the partition table in the given SECTOR of BLOCK and
+   scans it for partitions of interest to Pintos.
+
+   If SECTOR is 0, so that this is the top-level partition table
+   on BLOCK, then PRIMARY_EXTENDED_SECTOR is not meaningful;
+   otherwise, it should designate the sector of the top-level
+   extended partition table that was traversed to arrive at
+   SECTOR, for use in finding logical partitions (see the large
+   comment below).
+
+   PART_NR points to the number of non-empty primary or logical
+   partitions already encountered on BLOCK.  It is incremented as
+   partitions are found. */
+static void
+read_partition_table (struct block *block, block_sector_t sector,
+                      block_sector_t primary_extended_sector,
+                      int *part_nr)
+{
+  /* Format of a partition table entry.  See [Partitions]. */
+  struct partition_table_entry
+    {
+      uint8_t bootable;         /* 0x00=not bootable, 0x80=bootable. */
+      uint8_t start_chs[3];     /* Encoded starting cylinder, head, sector. */
+      uint8_t type;             /* Partition type (see partition_type_name). */
+      uint8_t end_chs[3];       /* Encoded ending cylinder, head, sector. */
+      uint32_t offset;          /* Start sector offset from partition table. */
+      uint32_t size;            /* Number of sectors. */
+    }
+  PACKED;
+
+  /* Partition table sector. */
+  struct partition_table
+    {
+      uint8_t loader[446];      /* Loader, in top-level partition table. */
+      struct partition_table_entry partitions[4];       /* Table entries. */
+      uint16_t signature;       /* Should be 0xaa55. */
+    }
+  PACKED;
+
+  struct partition_table *pt;
+  size_t i;
+
+  /* Check SECTOR validity. */
+  if (sector >= block_size (block))
+    {
+      printf ("%s: Partition table at sector %"PRDSNu" past end of device.\n",
+              block_name (block), sector);
+      return;
+    }
+
+  /* Read sector. */
+  ASSERT (sizeof *pt == BLOCK_SECTOR_SIZE);
+  pt = malloc (sizeof *pt);
+  if (pt == NULL)
+    PANIC ("Failed to allocate memory for partition table.");
+  block_read (block, 0, pt);
+
+  /* Check signature. */
+  if (pt->signature != 0xaa55)
+    {
+      if (primary_extended_sector == 0)
+        printf ("%s: Invalid partition table signature\n", block_name (block));
+      else
+        printf ("%s: Invalid extended partition table in sector %"PRDSNu"\n",
+                block_name (block), sector);
+      free (pt);
+      return;
+    }
+
+  /* Parse partitions. */
+  for (i = 0; i < sizeof pt->partitions / sizeof *pt->partitions; i++)
+    {
+      struct partition_table_entry *e = &pt->partitions[i];
+
+      if (e->size == 0 || e->type == 0)
+        {
+          /* Ignore empty partition. */
+        }
+      else if (e->type == 0x05       /* Extended partition. */
+               || e->type == 0x0f    /* Windows 98 extended partition. */
+               || e->type == 0x85    /* Linux extended partition. */
+               || e->type == 0xc5)   /* DR-DOS extended partition. */
+        {
+          printf ("%s: Extended partition in sector %"PRDSNu"\n",
+                  block_name (block), sector);
+
+          /* The interpretation of the offset field for extended
+             partitions is bizarre.  When the extended partition
+             table entry is in the master boot record, that is,
+             the device's primary partition table in sector 0, then
+             the offset is an absolute sector number.  Otherwise,
+             no matter how deep the partition table we're reading
+             is nested, the offset is relative to the start of
+             the extended partition that the MBR points to. */
+          if (sector == 0)
+            read_partition_table (block, e->offset, e->offset, part_nr);
+          else
+            read_partition_table (block, e->offset + primary_extended_sector,
+                                  primary_extended_sector, part_nr);
+        }
+      else
+        {
+          ++*part_nr;
+
+          found_partition (block, e->type, e->offset + sector,
+                           e->size, *part_nr);
+        }
+    }
+
+  free (pt);
+}
+
+/* We have found a primary or logical partition of the given TYPE
+   on BLOCK, starting at sector START and continuing for SIZE
+   sectors, which we are giving the partition number PART_NR.
+   Check whether this is a partition of interest to Pintos, and
+   if so then add it to the proper element of partitions[]. */
+static void
+found_partition (struct block *block, uint8_t part_type,
+                 block_sector_t start, block_sector_t size,
+                 int part_nr)
+{
+  if (start >= block_size (block))
+    printf ("%s%d: Partition starts past end of device (sector %"PRDSNu")\n",
+            block_name (block), part_nr, start);
+  else if (start + size < start || start + size > block_size (block))
+    printf ("%s%d: Partition end (%"PRDSNu") past end of device (%"PRDSNu")\n",
+            block_name (block), part_nr, start + size, block_size (block));
+  else
+    {
+      enum block_type type = (part_type == 0x20 ? BLOCK_KERNEL
+                              : part_type == 0x21 ? BLOCK_FILESYS
+                              : part_type == 0x22 ? BLOCK_SCRATCH
+                              : part_type == 0x23 ? BLOCK_SWAP
+                              : BLOCK_FOREIGN);
+      struct partition *p;
+      char extra_info[128];
+      char name[16];
+
+      p = malloc (sizeof *p);
+      if (p == NULL)
+        PANIC ("Failed to allocate memory for partition descriptor");
+      p->block = block;
+      p->start = start;
+
+      snprintf (name, sizeof name, "%s%d", block_name (block), part_nr);
+      snprintf (extra_info, sizeof extra_info, "%s (%02x)",
+                partition_type_name (part_type), part_type);
+      block_register (name, type, extra_info, size, &partition_operations, p);
+    }
+}
+
+/* Returns a human-readable name for the given partition TYPE. */
+static const char *
+partition_type_name (uint8_t type)
+{
+  /* Name of each known type of partition.
+     From util-linux-2.12r/fdisk/i386_sys_types.c.
+     This initializer makes use of a C99 feature that allows
+     array elements to be initialized by index. */
+  static const char *type_names[256] =
+    {
+      [0x00] = "Empty",
+      [0x01] = "FAT12",
+      [0x02] = "XENIX root",
+      [0x03] = "XENIX usr",
+      [0x04] = "FAT16 <32M",
+      [0x05] = "Extended",
+      [0x06] = "FAT16",
+      [0x07] = "HPFS/NTFS",
+      [0x08] = "AIX",
+      [0x09] = "AIX bootable",
+      [0x0a] = "OS/2 Boot Manager",
+      [0x0b] = "W95 FAT32",
+      [0x0c] = "W95 FAT32 (LBA)",
+      [0x0e] = "W95 FAT16 (LBA)",
+      [0x0f] = "W95 Ext'd (LBA)",
+      [0x10] = "OPUS",
+      [0x11] = "Hidden FAT12",
+      [0x12] = "Compaq diagnostics",
+      [0x14] = "Hidden FAT16 <32M",
+      [0x16] = "Hidden FAT16",
+      [0x17] = "Hidden HPFS/NTFS",
+      [0x18] = "AST SmartSleep",
+      [0x1b] = "Hidden W95 FAT32",
+      [0x1c] = "Hidden W95 FAT32 (LBA)",
+      [0x1e] = "Hidden W95 FAT16 (LBA)",
+      [0x20] = "Pintos OS kernel",
+      [0x21] = "Pintos file system",
+      [0x22] = "Pintos scratch",
+      [0x23] = "Pintos swap",
+      [0x24] = "NEC DOS",
+      [0x39] = "Plan 9",
+      [0x3c] = "PartitionMagic recovery",
+      [0x40] = "Venix 80286",
+      [0x41] = "PPC PReP Boot",
+      [0x42] = "SFS",
+      [0x4d] = "QNX4.x",
+      [0x4e] = "QNX4.x 2nd part",
+      [0x4f] = "QNX4.x 3rd part",
+      [0x50] = "OnTrack DM",
+      [0x51] = "OnTrack DM6 Aux1",
+      [0x52] = "CP/M",
+      [0x53] = "OnTrack DM6 Aux3",
+      [0x54] = "OnTrackDM6",
+      [0x55] = "EZ-Drive",
+      [0x56] = "Golden Bow",
+      [0x5c] = "Priam Edisk",
+      [0x61] = "SpeedStor",
+      [0x63] = "GNU HURD or SysV",
+      [0x64] = "Novell Netware 286",
+      [0x65] = "Novell Netware 386",
+      [0x70] = "DiskSecure Multi-Boot",
+      [0x75] = "PC/IX",
+      [0x80] = "Old Minix",
+      [0x81] = "Minix / old Linux",
+      [0x82] = "Linux swap / Solaris",
+      [0x83] = "Linux",
+      [0x84] = "OS/2 hidden C: drive",
+      [0x85] = "Linux extended",
+      [0x86] = "NTFS volume set",
+      [0x87] = "NTFS volume set",
+      [0x88] = "Linux plaintext",
+      [0x8e] = "Linux LVM",
+      [0x93] = "Amoeba",
+      [0x94] = "Amoeba BBT",
+      [0x9f] = "BSD/OS",
+      [0xa0] = "IBM Thinkpad hibernation",
+      [0xa5] = "FreeBSD",
+      [0xa6] = "OpenBSD",
+      [0xa7] = "NeXTSTEP",
+      [0xa8] = "Darwin UFS",
+      [0xa9] = "NetBSD",
+      [0xab] = "Darwin boot",
+      [0xb7] = "BSDI fs",
+      [0xb8] = "BSDI swap",
+      [0xbb] = "Boot Wizard hidden",
+      [0xbe] = "Solaris boot",
+      [0xbf] = "Solaris",
+      [0xc1] = "DRDOS/sec (FAT-12)",
+      [0xc4] = "DRDOS/sec (FAT-16 < 32M)",
+      [0xc6] = "DRDOS/sec (FAT-16)",
+      [0xc7] = "Syrinx",
+      [0xda] = "Non-FS data",
+      [0xdb] = "CP/M / CTOS / ...",
+      [0xde] = "Dell Utility",
+      [0xdf] = "BootIt",
+      [0xe1] = "DOS access",
+      [0xe3] = "DOS R/O",
+      [0xe4] = "SpeedStor",
+      [0xeb] = "BeOS fs",
+      [0xee] = "EFI GPT",
+      [0xef] = "EFI (FAT-12/16/32)",
+      [0xf0] = "Linux/PA-RISC boot",
+      [0xf1] = "SpeedStor",
+      [0xf4] = "SpeedStor",
+      [0xf2] = "DOS secondary",
+      [0xfd] = "Linux raid autodetect",
+      [0xfe] = "LANstep",
+      [0xff] = "BBT",
+    };
+
+  return type_names[type] != NULL ? type_names[type] : "Unknown";
+}
+
+/* Reads sector SECTOR from partition P into BUFFER, which must
+   have room for BLOCK_SECTOR_SIZE bytes. */
+static void
+partition_read (void *p_, block_sector_t sector, void *buffer)
+{
+  struct partition *p = p_;
+  block_read (p->block, p->start + sector, buffer);
+}
+
+/* Write sector SECTOR to partition P from BUFFER, which must
+   contain BLOCK_SECTOR_SIZE bytes.  Returns after the block has
+   acknowledged receiving the data. */
+static void
+partition_write (void *p_, block_sector_t sector, const void *buffer)
+{
+  struct partition *p = p_;
+  block_write (p->block, p->start + sector, buffer);
+}
+
+static struct block_operations partition_operations =
+  {
+    partition_read,
+    partition_write
+  };
diff --git a/src/devices/partition.h b/src/devices/partition.h
new file mode 100644 (file)
index 0000000..47fea4d
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef DEVICES_PARTITION_H
+#define DEVICES_PARTITION_H
+
+struct block;
+
+void partition_scan (struct block *);
+
+#endif /* devices/partition.h */
index 42b67df57bc533b1681d1afb964dc1634e3b0930..7ff9a95adbe080893eef291ca96c937afdf8d44b 100644 (file)
@@ -10,7 +10,7 @@
 #include "userprog/exception.h"
 #endif
 #ifdef FILESYS
-#include "devices/disk.h"
+#include "devices/block.h"
 #include "filesys/filesys.h"
 #endif
 
@@ -121,7 +121,7 @@ print_stats (void)
   timer_print_stats ();
   thread_print_stats ();
 #ifdef FILESYS
-  disk_print_stats ();
+  block_print_stats ();
 #endif
   console_print_stats ();
   kbd_print_stats ();
index d8050cd9718421f02f62173b6b190afb8f123da1..b3aa0059bd15f7245ae14abf9eb4cc56bc931c87 100644 (file)
@@ -1,13 +1,13 @@
 # -*- makefile -*-
 
-os.dsk: DEFINES = -DUSERPROG -DFILESYS
+kernel.bin: DEFINES = -DUSERPROG -DFILESYS
 KERNEL_SUBDIRS = threads devices lib lib/kernel userprog filesys
 TEST_SUBDIRS = tests/userprog tests/filesys/base tests/filesys/extended
 GRADING_FILE = $(SRCDIR)/tests/filesys/Grading.no-vm
 SIMULATOR = --qemu
 
 # Uncomment the lines below to enable VM.
-#os.dsk: DEFINES += -DVM
+#kernel.bin: DEFINES += -DVM
 #KERNEL_SUBDIRS += vm
 #TEST_SUBDIRS += tests/vm
 #GRADING_FILE = $(SRCDIR)/tests/filesys/Grading.with-vm
index 0d265d5da78b7be2ca705a5a9b7eeacfbcd5dfd2..030c1c9b0dbaf9b3a41a4decaadd7c76793b1d63 100644 (file)
@@ -16,7 +16,7 @@ struct dir
 /* A single directory entry. */
 struct dir_entry 
   {
-    disk_sector_t inode_sector;         /* Sector number of header. */
+    block_sector_t inode_sector;        /* Sector number of header. */
     char name[NAME_MAX + 1];            /* Null terminated file name. */
     bool in_use;                        /* In use or free? */
   };
@@ -24,7 +24,7 @@ struct dir_entry
 /* Creates a directory with space for ENTRY_CNT entries in the
    given SECTOR.  Returns true if successful, false on failure. */
 bool
-dir_create (disk_sector_t sector, size_t entry_cnt) 
+dir_create (block_sector_t sector, size_t entry_cnt)
 {
   return inode_create (sector, entry_cnt * sizeof (struct dir_entry));
 }
@@ -139,12 +139,12 @@ dir_lookup (const struct dir *dir, const char *name,
    Fails if NAME is invalid (i.e. too long) or a disk or memory
    error occurs. */
 bool
-dir_add (struct dir *dir, const char *name, disk_sector_t inode_sector) 
+dir_add (struct dir *dir, const char *name, block_sector_t inode_sector)
 {
   struct dir_entry e;
   off_t ofs;
   bool success = false;
-  
+
   ASSERT (dir != NULL);
   ASSERT (name != NULL);
 
index 7955937eae3e3b77f5eff1007f3e7f609b3be755..930acf986df4b1a647cb8d7cbd01c522d1b2b786 100644 (file)
@@ -3,7 +3,7 @@
 
 #include <stdbool.h>
 #include <stddef.h>
-#include "devices/disk.h"
+#include "devices/block.h"
 
 /* Maximum length of a file name component.
    This is the traditional UNIX maximum length.
@@ -14,7 +14,7 @@
 struct inode;
 
 /* Opening and closing directories. */
-bool dir_create (disk_sector_t sector, size_t entry_cnt);
+bool dir_create (block_sector_t sector, size_t entry_cnt);
 struct dir *dir_open (struct inode *);
 struct dir *dir_open_root (void);
 struct dir *dir_reopen (struct dir *);
@@ -23,7 +23,7 @@ struct inode *dir_get_inode (struct dir *);
 
 /* Reading and writing. */
 bool dir_lookup (const struct dir *, const char *name, struct inode **);
-bool dir_add (struct dir *, const char *name, disk_sector_t);
+bool dir_add (struct dir *, const char *name, block_sector_t);
 bool dir_remove (struct dir *, const char *name);
 bool dir_readdir (struct dir *, char name[NAME_MAX + 1]);
 
index fedda08e1d6730421a129e286117298821fd75fb..7a53f5ffd924ca7b1b384fd9b46b3256e14f30e6 100644 (file)
@@ -6,10 +6,9 @@
 #include "filesys/free-map.h"
 #include "filesys/inode.h"
 #include "filesys/directory.h"
-#include "devices/disk.h"
 
-/* The disk that contains the file system. */
-struct disk *filesys_disk;
+/* Partition that contains the file system. */
+struct block *fs_device;
 
 static void do_format (void);
 
@@ -18,9 +17,9 @@ static void do_format (void);
 void
 filesys_init (bool format) 
 {
-  filesys_disk = disk_get (0, 1);
-  if (filesys_disk == NULL)
-    PANIC ("hd0:1 (hdb) not present, file system initialization failed");
+  fs_device = block_get_role (BLOCK_FILESYS);
+  if (fs_device == NULL)
+    PANIC ("No file system device found, can't initialize file system.");
 
   inode_init ();
   free_map_init ();
@@ -46,7 +45,7 @@ filesys_done (void)
 bool
 filesys_create (const char *name, off_t initial_size) 
 {
-  disk_sector_t inode_sector = 0;
+  block_sector_t inode_sector = 0;
   struct dir *dir = dir_open_root ();
   bool success = (dir != NULL
                   && free_map_allocate (1, &inode_sector)
index caef83c45c17f4986a3fc8838769a64d7a1e7606..c1cda84ecc324fbe70b4e9e3e729ed9ade89c24b 100644 (file)
@@ -8,8 +8,8 @@
 #define FREE_MAP_SECTOR 0       /* Free map file inode sector. */
 #define ROOT_DIR_SECTOR 1       /* Root directory file inode sector. */
 
-/* Disk used for file system. */
-extern struct disk *filesys_disk;
+/* Block device that contains the file system. */
+struct block *fs_device;
 
 void filesys_init (bool format);
 void filesys_done (void);
index 1cd917522c076cb874e0b121a87d3b7633e28d21..34f2060b1860f23d20ea29f5ed687ec4cbb7a139 100644 (file)
@@ -6,15 +6,15 @@
 #include "filesys/inode.h"
 
 static struct file *free_map_file;   /* Free map file. */
-static struct bitmap *free_map;      /* Free map, one bit per disk sector. */
+static struct bitmap *free_map;      /* Free map, one bit per sector. */
 
 /* Initializes the free map. */
 void
 free_map_init (void) 
 {
-  free_map = bitmap_create (disk_size (filesys_disk));
+  free_map = bitmap_create (block_size (fs_device));
   if (free_map == NULL)
-    PANIC ("bitmap creation failed--disk is too large");
+    PANIC ("bitmap creation failed--file system device is too large");
   bitmap_mark (free_map, FREE_MAP_SECTOR);
   bitmap_mark (free_map, ROOT_DIR_SECTOR);
 }
@@ -24,9 +24,9 @@ free_map_init (void)
    Returns true if successful, false if all sectors were
    available. */
 bool
-free_map_allocate (size_t cnt, disk_sector_t *sectorp) 
+free_map_allocate (size_t cnt, block_sector_t *sectorp)
 {
-  disk_sector_t sector = bitmap_scan_and_flip (free_map, 0, cnt, false);
+  block_sector_t sector = bitmap_scan_and_flip (free_map, 0, cnt, false);
   if (sector != BITMAP_ERROR
       && free_map_file != NULL
       && !bitmap_write (free_map, free_map_file))
@@ -41,7 +41,7 @@ free_map_allocate (size_t cnt, disk_sector_t *sectorp)
 
 /* Makes CNT sectors starting at SECTOR available for use. */
 void
-free_map_release (disk_sector_t sector, size_t cnt)
+free_map_release (block_sector_t sector, size_t cnt)
 {
   ASSERT (bitmap_all (free_map, sector, cnt));
   bitmap_set_multiple (free_map, sector, cnt, false);
index ce08f5c8dd9ab54b2742925cf3f533df76106688..316cd1c8cb851c6652a8910a861826d8c2a95230 100644 (file)
@@ -3,7 +3,7 @@
 
 #include <stdbool.h>
 #include <stddef.h>
-#include "devices/disk.h"
+#include "devices/block.h"
 
 void free_map_init (void);
 void free_map_read (void);
@@ -11,7 +11,7 @@ void free_map_create (void);
 void free_map_open (void);
 void free_map_close (void);
 
-bool free_map_allocate (size_t, disk_sector_t *);
-void free_map_release (disk_sector_t, size_t);
+bool free_map_allocate (size_t, block_sector_t *);
+void free_map_release (block_sector_t, size_t);
 
 #endif /* filesys/free-map.h */
index bf2c15466af77514211de9ca9e79b54f677ed905..447f29131c8395cb36f30c0fb24fff0f7f19fccb 100644 (file)
@@ -7,7 +7,6 @@
 #include "filesys/directory.h"
 #include "filesys/file.h"
 #include "filesys/filesys.h"
-#include "devices/disk.h"
 #include "threads/malloc.h"
 #include "threads/palloc.h"
 #include "threads/vaddr.h"
@@ -67,28 +66,29 @@ fsutil_rm (char **argv)
     PANIC ("%s: delete failed\n", file_name);
 }
 
-/* Extracts a ustar-format tar archive from the scratch disk, hdc
-   or hd1:0, into the Pintos file system. */
+/* Extracts a ustar-format tar archive from the scratch block
+   device into the Pintos file system. */
 void
 fsutil_extract (char **argv UNUSED) 
 {
-  static disk_sector_t sector = 0;
+  static block_sector_t sector = 0;
 
-  struct disk *src;
+  struct block *src;
   void *header, *data;
 
   /* Allocate buffers. */
-  header = malloc (DISK_SECTOR_SIZE);
-  data = malloc (DISK_SECTOR_SIZE);
+  header = malloc (BLOCK_SECTOR_SIZE);
+  data = malloc (BLOCK_SECTOR_SIZE);
   if (header == NULL || data == NULL)
     PANIC ("couldn't allocate buffers");
 
-  /* Open source disk. */
-  src = disk_get (1, 0);
+  /* Open source block device. */
+  src = block_get_role (BLOCK_SCRATCH);
   if (src == NULL)
-    PANIC ("couldn't open scratch disk (hdc or hd1:0)");
+    PANIC ("couldn't open scratch device");
 
-  printf ("Extracting ustar archive from scratch disk into file system...\n");
+  printf ("Extracting ustar archive from scratch device "
+          "into file system...\n");
 
   for (;;)
     {
@@ -98,7 +98,7 @@ fsutil_extract (char **argv UNUSED)
       int size;
 
       /* Read and parse ustar header. */
-      disk_read (src, sector++, header);
+      block_read (src, sector++, header);
       error = ustar_parse_header (header, &file_name, &type, &size);
       if (error != NULL)
         PANIC ("bad ustar header in sector %"PRDSNu" (%s)", sector - 1, error);
@@ -126,10 +126,10 @@ fsutil_extract (char **argv UNUSED)
           /* Do copy. */
           while (size > 0)
             {
-              int chunk_size = (size > DISK_SECTOR_SIZE
-                                ? DISK_SECTOR_SIZE
+              int chunk_size = (size > BLOCK_SECTOR_SIZE
+                                ? BLOCK_SECTOR_SIZE
                                 : size);
-              disk_read (src, sector++, data);
+              block_read (src, sector++, data);
               if (file_write (dst, data, chunk_size) != chunk_size)
                 PANIC ("%s: write failed with %d bytes unwritten",
                        file_name, size);
@@ -141,42 +141,42 @@ fsutil_extract (char **argv UNUSED)
         }
     }
 
-  /* Erase the ustar header from the start of the disk, so that
-     the extraction operation is idempotent.  We erase two blocks
-     because two blocks of zeros are the ustar end-of-archive
-     marker. */
+  /* Erase the ustar header from the start of the block device,
+     so that the extraction operation is idempotent.  We erase
+     two blocks because two blocks of zeros are the ustar
+     end-of-archive marker. */
   printf ("Erasing ustar archive...\n");
-  memset (header, 0, DISK_SECTOR_SIZE);
-  disk_write (src, 0, header);
-  disk_write (src, 1, header);
+  memset (header, 0, BLOCK_SECTOR_SIZE);
+  block_write (src, 0, header);
+  block_write (src, 1, header);
 
   free (data);
   free (header);
 }
 
 /* Copies file FILE_NAME from the file system to the scratch
-   disk, in ustar format.
+   device, in ustar format.
 
    The first call to this function will write starting at the
-   beginning of the scratch disk.  Later calls advance across the
-   disk.  This position is independent of that used for
+   beginning of the scratch device.  Later calls advance across
+   the device.  This position is independent of that used for
    fsutil_extract(), so `extract' should precede all
    `append's. */
 void
 fsutil_append (char **argv)
 {
-  static disk_sector_t sector = 0;
+  static block_sector_t sector = 0;
 
   const char *file_name = argv[1];
   void *buffer;
   struct file *src;
-  struct disk *dst;
+  struct block *dst;
   off_t size;
 
-  printf ("Appending '%s' to ustar archive on scratch disk...\n", file_name);
+  printf ("Appending '%s' to ustar archive on scratch device...\n", file_name);
 
   /* Allocate buffer. */
-  buffer = malloc (DISK_SECTOR_SIZE);
+  buffer = malloc (BLOCK_SECTOR_SIZE);
   if (buffer == NULL)
     PANIC ("couldn't allocate buffer");
 
@@ -186,35 +186,35 @@ fsutil_append (char **argv)
     PANIC ("%s: open failed", file_name);
   size = file_length (src);
 
-  /* Open target disk. */
-  dst = disk_get (1, 0);
+  /* Open target block device. */
+  dst = block_get_role (BLOCK_SCRATCH);
   if (dst == NULL)
-    PANIC ("couldn't open target disk (hdc or hd1:0)");
+    PANIC ("couldn't open scratch device");
   
   /* Write ustar header to first sector. */
   if (!ustar_make_header (file_name, USTAR_REGULAR, size, buffer))
     PANIC ("%s: name too long for ustar format", file_name);
-  disk_write (dst, sector++, buffer);
+  block_write (dst, sector++, buffer);
 
   /* Do copy. */
   while (size > 0) 
     {
-      int chunk_size = size > DISK_SECTOR_SIZE ? DISK_SECTOR_SIZE : size;
-      if (sector >= disk_size (dst))
-        PANIC ("%s: out of space on scratch disk", file_name);
+      int chunk_size = size > BLOCK_SECTOR_SIZE ? BLOCK_SECTOR_SIZE : size;
+      if (sector >= block_size (dst))
+        PANIC ("%s: out of space on scratch device", file_name);
       if (file_read (src, buffer, chunk_size) != chunk_size)
         PANIC ("%s: read failed with %"PROTd" bytes unread", file_name, size);
-      memset (buffer + chunk_size, 0, DISK_SECTOR_SIZE - chunk_size);
-      disk_write (dst, sector++, buffer);
+      memset (buffer + chunk_size, 0, BLOCK_SECTOR_SIZE - chunk_size);
+      block_write (dst, sector++, buffer);
       size -= chunk_size;
     }
 
   /* Write ustar end-of-archive marker, which is two consecutive
      sectors full of zeros.  Don't advance our position past
      them, though, in case we have more files to append. */
-  memset (buffer, 0, DISK_SECTOR_SIZE);
-  disk_write (dst, sector, buffer);
-  disk_write (dst, sector, buffer + 1);
+  memset (buffer, 0, BLOCK_SECTOR_SIZE);
+  block_write (dst, sector, buffer);
+  block_write (dst, sector, buffer + 1);
 
   /* Finish up. */
   file_close (src);
index d890ed8d3c7f0375dc9f3a6a1178254d2c2db011..34635637114212a54a5fb1313a6f4a1484b57ff2 100644 (file)
 #define INODE_MAGIC 0x494e4f44
 
 /* On-disk inode.
-   Must be exactly DISK_SECTOR_SIZE bytes long. */
+   Must be exactly BLOCK_SECTOR_SIZE bytes long. */
 struct inode_disk
   {
-    disk_sector_t start;                /* First data sector. */
+    block_sector_t start;               /* First data sector. */
     off_t length;                       /* File size in bytes. */
     unsigned magic;                     /* Magic number. */
     uint32_t unused[125];               /* Not used. */
@@ -25,30 +25,30 @@ struct inode_disk
 static inline size_t
 bytes_to_sectors (off_t size)
 {
-  return DIV_ROUND_UP (size, DISK_SECTOR_SIZE);
+  return DIV_ROUND_UP (size, BLOCK_SECTOR_SIZE);
 }
 
 /* In-memory inode. */
 struct inode 
   {
     struct list_elem elem;              /* Element in inode list. */
-    disk_sector_t sector;               /* Sector number of disk location. */
+    block_sector_t sector;              /* Sector number of disk location. */
     int open_cnt;                       /* Number of openers. */
     bool removed;                       /* True if deleted, false otherwise. */
     int deny_write_cnt;                 /* 0: writes ok, >0: deny writes. */
     struct inode_disk data;             /* Inode content. */
   };
 
-/* Returns the disk sector that contains byte offset POS within
-   INODE.
+/* Returns the block device sector that contains byte offset POS
+   within INODE.
    Returns -1 if INODE does not contain data for a byte at offset
    POS. */
-static disk_sector_t
+static block_sector_t
 byte_to_sector (const struct inode *inode, off_t pos) 
 {
   ASSERT (inode != NULL);
   if (pos < inode->data.length)
-    return inode->data.start + pos / DISK_SECTOR_SIZE;
+    return inode->data.start + pos / BLOCK_SECTOR_SIZE;
   else
     return -1;
 }
@@ -66,11 +66,11 @@ inode_init (void)
 
 /* Initializes an inode with LENGTH bytes of data and
    writes the new inode to sector SECTOR on the file system
-   disk.
+   device.
    Returns true if successful.
    Returns false if memory or disk allocation fails. */
 bool
-inode_create (disk_sector_t sector, off_t length)
+inode_create (block_sector_t sector, off_t length)
 {
   struct inode_disk *disk_inode = NULL;
   bool success = false;
@@ -79,7 +79,7 @@ inode_create (disk_sector_t sector, off_t length)
 
   /* If this assertion fails, the inode structure is not exactly
      one sector in size, and you should fix that. */
-  ASSERT (sizeof *disk_inode == DISK_SECTOR_SIZE);
+  ASSERT (sizeof *disk_inode == BLOCK_SECTOR_SIZE);
 
   disk_inode = calloc (1, sizeof *disk_inode);
   if (disk_inode != NULL)
@@ -87,16 +87,16 @@ inode_create (disk_sector_t sector, off_t length)
       size_t sectors = bytes_to_sectors (length);
       disk_inode->length = length;
       disk_inode->magic = INODE_MAGIC;
-      if (free_map_allocate (sectors, &disk_inode->start))
+      if (free_map_allocate (sectors, &disk_inode->start)) 
         {
-          disk_write (filesys_disk, sector, disk_inode);
+          block_write (fs_device, sector, disk_inode);
           if (sectors > 0) 
             {
-              static char zeros[DISK_SECTOR_SIZE];
+              static char zeros[BLOCK_SECTOR_SIZE];
               size_t i;
               
               for (i = 0; i < sectors; i++) 
-                disk_write (filesys_disk, disk_inode->start + i, zeros); 
+                block_write (fs_device, disk_inode->start + i, zeros);
             }
           success = true; 
         } 
@@ -109,7 +109,7 @@ inode_create (disk_sector_t sector, off_t length)
    and returns a `struct inode' that contains it.
    Returns a null pointer if memory allocation fails. */
 struct inode *
-inode_open (disk_sector_t sector) 
+inode_open (block_sector_t sector)
 {
   struct list_elem *e;
   struct inode *inode;
@@ -137,7 +137,7 @@ inode_open (disk_sector_t sector)
   inode->open_cnt = 1;
   inode->deny_write_cnt = 0;
   inode->removed = false;
-  disk_read (filesys_disk, inode->sector, &inode->data);
+  block_read (fs_device, inode->sector, &inode->data);
   return inode;
 }
 
@@ -151,7 +151,7 @@ inode_reopen (struct inode *inode)
 }
 
 /* Returns INODE's inode number. */
-disk_sector_t
+block_sector_t
 inode_get_inumber (const struct inode *inode)
 {
   return inode->sector;
@@ -207,12 +207,12 @@ inode_read_at (struct inode *inode, void *buffer_, off_t size, off_t offset)
   while (size > 0) 
     {
       /* Disk sector to read, starting byte offset within sector. */
-      disk_sector_t sector_idx = byte_to_sector (inode, offset);
-      int sector_ofs = offset % DISK_SECTOR_SIZE;
+      block_sector_t sector_idx = byte_to_sector (inode, offset);
+      int sector_ofs = offset % BLOCK_SECTOR_SIZE;
 
       /* Bytes left in inode, bytes left in sector, lesser of the two. */
       off_t inode_left = inode_length (inode) - offset;
-      int sector_left = DISK_SECTOR_SIZE - sector_ofs;
+      int sector_left = BLOCK_SECTOR_SIZE - sector_ofs;
       int min_left = inode_left < sector_left ? inode_left : sector_left;
 
       /* Number of bytes to actually copy out of this sector. */
@@ -220,10 +220,10 @@ inode_read_at (struct inode *inode, void *buffer_, off_t size, off_t offset)
       if (chunk_size <= 0)
         break;
 
-      if (sector_ofs == 0 && chunk_size == DISK_SECTOR_SIZE) 
+      if (sector_ofs == 0 && chunk_size == BLOCK_SECTOR_SIZE)
         {
           /* Read full sector directly into caller's buffer. */
-          disk_read (filesys_disk, sector_idx, buffer + bytes_read); 
+          block_read (fs_device, sector_idx, buffer + bytes_read);
         }
       else 
         {
@@ -231,11 +231,11 @@ inode_read_at (struct inode *inode, void *buffer_, off_t size, off_t offset)
              into caller's buffer. */
           if (bounce == NULL) 
             {
-              bounce = malloc (DISK_SECTOR_SIZE);
+              bounce = malloc (BLOCK_SECTOR_SIZE);
               if (bounce == NULL)
                 break;
             }
-          disk_read (filesys_disk, sector_idx, bounce);
+          block_read (fs_device, sector_idx, bounce);
           memcpy (buffer + bytes_read, bounce + sector_ofs, chunk_size);
         }
       
@@ -268,12 +268,12 @@ inode_write_at (struct inode *inode, const void *buffer_, off_t size,
   while (size > 0) 
     {
       /* Sector to write, starting byte offset within sector. */
-      disk_sector_t sector_idx = byte_to_sector (inode, offset);
-      int sector_ofs = offset % DISK_SECTOR_SIZE;
+      block_sector_t sector_idx = byte_to_sector (inode, offset);
+      int sector_ofs = offset % BLOCK_SECTOR_SIZE;
 
       /* Bytes left in inode, bytes left in sector, lesser of the two. */
       off_t inode_left = inode_length (inode) - offset;
-      int sector_left = DISK_SECTOR_SIZE - sector_ofs;
+      int sector_left = BLOCK_SECTOR_SIZE - sector_ofs;
       int min_left = inode_left < sector_left ? inode_left : sector_left;
 
       /* Number of bytes to actually write into this sector. */
@@ -281,17 +281,17 @@ inode_write_at (struct inode *inode, const void *buffer_, off_t size,
       if (chunk_size <= 0)
         break;
 
-      if (sector_ofs == 0 && chunk_size == DISK_SECTOR_SIZE) 
+      if (sector_ofs == 0 && chunk_size == BLOCK_SECTOR_SIZE)
         {
           /* Write full sector directly to disk. */
-          disk_write (filesys_disk, sector_idx, buffer + bytes_written); 
+          block_write (fs_device, sector_idx, buffer + bytes_written);
         }
       else 
         {
           /* We need a bounce buffer. */
           if (bounce == NULL) 
             {
-              bounce = malloc (DISK_SECTOR_SIZE);
+              bounce = malloc (BLOCK_SECTOR_SIZE);
               if (bounce == NULL)
                 break;
             }
@@ -300,11 +300,11 @@ inode_write_at (struct inode *inode, const void *buffer_, off_t size,
              we're writing, then we need to read in the sector
              first.  Otherwise we start with a sector of all zeros. */
           if (sector_ofs > 0 || chunk_size < sector_left) 
-            disk_read (filesys_disk, sector_idx, bounce);
+            block_read (fs_device, sector_idx, bounce);
           else
-            memset (bounce, 0, DISK_SECTOR_SIZE);
+            memset (bounce, 0, BLOCK_SECTOR_SIZE);
           memcpy (bounce + sector_ofs, buffer + bytes_written, chunk_size);
-          disk_write (filesys_disk, sector_idx, bounce); 
+          block_write (fs_device, sector_idx, bounce);
         }
 
       /* Advance. */
index be7df634ef499bf83390647d75a92edb4532ecc8..cb42310fa8d77dcc438a4cd689361b1e126bacbe 100644 (file)
@@ -3,15 +3,15 @@
 
 #include <stdbool.h>
 #include "filesys/off_t.h"
-#include "devices/disk.h"
+#include "devices/block.h"
 
 struct bitmap;
 
 void inode_init (void);
-bool inode_create (disk_sector_t, off_t);
-struct inode *inode_open (disk_sector_t);
+bool inode_create (block_sector_t, off_t);
+struct inode *inode_open (block_sector_t);
 struct inode *inode_reopen (struct inode *);
-disk_sector_t inode_get_inumber (const struct inode *);
+block_sector_t inode_get_inumber (const struct inode *);
 void inode_close (struct inode *);
 void inode_remove (struct inode *);
 off_t inode_read_at (struct inode *, void *, off_t size, off_t offset);
index 2388f9acdbf8e553d79ff6a27b93703020a3739e..82efbb5b1c19053a4b7c68723f358bb413ece4c2 100644 (file)
@@ -109,6 +109,19 @@ struct list
         ((STRUCT *) ((uint8_t *) &(LIST_ELEM)->next     \
                      - offsetof (STRUCT, MEMBER.next)))
 
+/* List initialization.
+
+   A list may be initialized by calling list_init():
+
+       struct list my_list;
+       list_init (&my_list);
+
+   or with an initializer using LIST_INITIALIZER:
+
+       struct list my_list = LIST_INITIALIZER (my_list); */
+#define LIST_INITIALIZER(NAME) { { NULL, &(NAME).tail }, \
+                                 { &(NAME).head, NULL } }
+
 void list_init (struct list *);
 
 /* List traversal. */
index af80e51e527efc00897eae04ca6fe6642fd89d1b..8927c50555d9993ae1ac952e21bc3fe7deb93f7e 100644 (file)
@@ -635,3 +635,21 @@ hex_dump (uintptr_t ofs, const void *buf_, size_t size, bool ascii)
       size -= n;
     }
 }
+
+/* Prints SIZE, which represents a number of bytes, in a
+   human-readable format, e.g. "256 kB". */
+void
+print_human_readable_size (uint64_t size) 
+{
+  if (size == 1)
+    printf ("1 byte");
+  else 
+    {
+      static const char *factors[] = {"bytes", "kB", "MB", "GB", "TB", NULL};
+      const char **fp;
+
+      for (fp = factors; size >= 1024 && fp[1] != NULL; fp++)
+        size /= 1024;
+      printf ("%"PRIu64" %s", size, *fp);
+    }
+}
index 8288ff04ac8b18c677eb0ee58a0267691654e04c..2739c0a2959de450fc348554f1730c2a0decc22c 100644 (file)
@@ -25,6 +25,7 @@ int puts (const char *);
 
 /* Nonstandard functions. */
 void hex_dump (uintptr_t ofs, const void *, size_t size, bool ascii);
+void print_human_readable_size (uint64_t sz);
 
 /* Internal functions. */
 void __vprintf (const char *format, va_list args,
index 76d63f676cd838f76ffc20a75508ad377428ddca..358e69724b156b15dc98c76301367015ee3b6a59 100644 (file)
@@ -55,13 +55,13 @@ TESTCMD = pintos -v -k -T $(TIMEOUT)
 TESTCMD += $(SIMULATOR)
 TESTCMD += $(PINTOSOPTS)
 ifeq ($(filter userprog, $(KERNEL_SUBDIRS)), userprog)
-TESTCMD += --fs-disk=$(FSDISK)
+TESTCMD += $(FILESYSSOURCE)
 TESTCMD += $(foreach file,$(PUTFILES),-p $(file) -a $(notdir $(file)))
 endif
 ifeq ($(filter vm, $(KERNEL_SUBDIRS)), vm)
-TESTCMD += --swap-disk=4
+TESTCMD += --swap-size=4
 endif
-TESTCMD += -- -q 
+TESTCMD += -- -q
 TESTCMD += $(KERNELFLAGS)
 ifeq ($(filter userprog, $(KERNEL_SUBDIRS)), userprog)
 TESTCMD += -f
@@ -69,7 +69,7 @@ endif
 TESTCMD += $(if $($(TEST)_ARGS),run '$(*F) $($(TEST)_ARGS)',run $(*F))
 TESTCMD += < /dev/null
 TESTCMD += 2> $(TEST).errors $(if $(VERBOSE),|tee,>) $(TEST).output
-%.output: os.dsk
+%.output: kernel.bin loader.bin
        $(TESTCMD)
 
 %.result: %.ck %.output
index 75a872baa62a5fdebb143b5d32b0e8d3feea54a2..e03b98dc4fbfc69f1808ba2e8f13a902e75e1ea7 100644 (file)
@@ -20,7 +20,7 @@ $(foreach prog,$(tests/filesys/extended_TESTS),               \
        $(eval $(prog)_PUTFILES += tests/filesys/extended/tar))
 # The version of GNU make 3.80 on vine barfs if this is split at
 # the last comma.
-$(foreach test,$(tests/filesys/extended_TESTS),$(eval $(test).output: FSDISK = tmp.dsk))
+$(foreach test,$(tests/filesys/extended_TESTS),$(eval $(test).output: FILESYSSOURCE = --disk=tmp.dsk))
 
 tests/filesys/extended/dir-mk-tree_SRC += tests/filesys/extended/mk-tree.c
 tests/filesys/extended/dir-rm-tree_SRC += tests/filesys/extended/mk-tree.c
@@ -34,10 +34,10 @@ GETTIMEOUT = 60
 GETCMD = pintos -v -k -T $(GETTIMEOUT)
 GETCMD += $(PINTOSOPTS)
 GETCMD += $(SIMULATOR)
-GETCMD += --fs-disk=$(FSDISK)
+GETCMD += $(FILESYSSOURCE)
 GETCMD += -g fs.tar -a $(TEST).tar
 ifeq ($(filter vm, $(KERNEL_SUBDIRS)), vm)
-GETCMD += --swap-disk=4
+GETCMD += --swap-size=4
 endif
 GETCMD += -- -q
 GETCMD += $(KERNELFLAGS)
@@ -45,9 +45,9 @@ GETCMD += run 'tar fs.tar /'
 GETCMD += < /dev/null
 GETCMD += 2> $(TEST)-persistence.errors $(if $(VERBOSE),|tee,>) $(TEST)-persistence.output
 
-tests/filesys/extended/%.output: os.dsk
+tests/filesys/extended/%.output: kernel.bin
        rm -f tmp.dsk
-       pintos-mkdisk tmp.dsk 2
+       pintos-mkdisk tmp.dsk --filesys-size=2
        $(TESTCMD)
        $(GETCMD)
        rm -f tmp.dsk
index 8d64c056c9642cb07bfdabe96843b7f3ea61d640..caadd90edbef093b184e682841f182b335eef99e 100644 (file)
@@ -1,7 +1,7 @@
 # -*- makefile -*-
 
-tests/%.output: FSDISK = 2
-tests/%.output: PUTFILES = $(filter-out os.dsk, $^)
+tests/%.output: FILESYSSOURCE = --filesys-size=2
+tests/%.output: PUTFILES = $(filter-out kernel.bin loader.bin, $^)
 
 tests/userprog_TESTS = $(addprefix tests/userprog/,args-none           \
 args-single args-multiple args-many args-dbl-space sc-bad-sp           \
index dc45c2a13dbc24e62a1dda1d4835af3a7b6a52a9..310c240748ed69c653b97a88f828e6d0bb8b1a9a 100644 (file)
@@ -1,6 +1,6 @@
 # -*- makefile -*-
 
-os.dsk: DEFINES =
+kernel.bin: DEFINES =
 KERNEL_SUBDIRS = threads devices lib lib/kernel $(TEST_SUBDIRS)
 TEST_SUBDIRS = tests/threads
 GRADING_FILE = $(SRCDIR)/tests/threads/Grading
index d2d3e087de286cb8f5b48fd960d1568ae2d1cab4..cebec2c817f5954b9accf9558dbd36822c1fb8f4 100644 (file)
@@ -1,10 +1,10 @@
 #include "threads/init.h"
 #include <console.h>
 #include <debug.h>
+#include <inttypes.h>
 #include <limits.h>
 #include <random.h>
 #include <stddef.h>
-#include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include "tests/threads/tests.h"
 #endif
 #ifdef FILESYS
-#include "devices/disk.h"
+#include "devices/block.h"
+#include "devices/ide.h"
 #include "filesys/filesys.h"
 #include "filesys/fsutil.h"
 #endif
 
-/* Amount of physical memory, in 4 kB pages. */
-size_t init_ram_pages;
-
 /* Page directory with kernel mappings only. */
 uint32_t *init_page_dir;
 
 #ifdef FILESYS
 /* -f: Format the file system? */
 static bool format_filesys;
+
+/* -filesys, -scratch, -swap: Names of block devices to use,
+   overriding the defaults. */
+static const char *filesys_bdev_name;
+static const char *scratch_bdev_name;
+#ifdef VM
+static const char *swap_bdev_name;
 #endif
+#endif /* FILESYS */
 
 /* -ul: Maximum number of pages to put into palloc's user pool. */
 static size_t user_page_limit = SIZE_MAX;
 
-static void ram_init (void);
+static void bss_init (void);
 static void paging_init (void);
 
 static char **read_command_line (void);
@@ -59,6 +65,11 @@ static char **parse_options (char **argv);
 static void run_actions (char **argv);
 static void usage (void);
 
+#ifdef FILESYS
+static void locate_block_devices (void);
+static void locate_block_device (enum block_type, const char *name);
+#endif
+
 int main (void) NO_RETURN;
 
 /* Pintos main program. */
@@ -66,9 +77,9 @@ int
 main (void)
 {
   char **argv;
-  
-  /* Clear BSS and get machine's RAM size. */  
-  ram_init ();
+
+  /* Clear BSS. */  
+  bss_init ();
 
   /* Break command line into arguments and parse options. */
   argv = read_command_line ();
@@ -80,7 +91,7 @@ main (void)
   console_init ();  
 
   /* Greet user. */
-  printf ("Pintos booting with %'zu kB RAM...\n",
+  printf ("Pintos booting with %'"PRIu32" kB RAM...\n",
           init_ram_pages * PGSIZE / 1024);
 
   /* Initialize memory system. */
@@ -111,7 +122,8 @@ main (void)
 
 #ifdef FILESYS
   /* Initialize file system. */
-  disk_init ();
+  ide_init ();
+  locate_block_devices ();
   filesys_init (format_filesys);
 #endif
 
@@ -125,32 +137,23 @@ main (void)
   thread_exit ();
 }
 \f
-/* Clear BSS and obtain RAM size from loader. */
+/* Clear the "BSS", a segment that should be initialized to
+   zeros.  It isn't actually stored on disk or zeroed by the
+   kernel loader, so we have to zero it ourselves.
+
+   The start and end of the BSS segment is recorded by the
+   linker as _start_bss and _end_bss.  See kernel.lds. */
 static void
-ram_init (void) 
+bss_init (void) 
 {
-  /* The "BSS" is a segment that should be initialized to zeros.
-     It isn't actually stored on disk or zeroed by the kernel
-     loader, so we have to zero it ourselves.
-
-     The start and end of the BSS segment is recorded by the
-     linker as _start_bss and _end_bss.  See kernel.lds. */
   extern char _start_bss, _end_bss;
   memset (&_start_bss, 0, &_end_bss - &_start_bss);
-
-  /* Get RAM size from loader.  See loader.S. */
-  init_ram_pages = *(uint32_t *) ptov (LOADER_RAM_PGS);
 }
 
 /* Populates the base page directory and page table with the
    kernel virtual mapping, and then sets up the CPU to use the
    new page directory.  Points init_page_dir to the page
-   directory it creates.
-
-   At the time this function is called, the active page table
-   (set up by loader.S) only maps the first 4 MB of RAM, so we
-   should not try to use extravagant amounts of memory.
-   Fortunately, there is no need to do so. */
+   directory it creates. */
 static void
 paging_init (void)
 {
@@ -240,6 +243,14 @@ parse_options (char **argv)
 #ifdef FILESYS
       else if (!strcmp (name, "-f"))
         format_filesys = true;
+      else if (!strcmp (name, "-filesys"))
+        filesys_bdev_name = value;
+      else if (!strcmp (name, "-scratch"))
+        scratch_bdev_name = value;
+#ifdef VM
+      else if (!strcmp (name, "-swap"))
+        swap_bdev_name = value;
+#endif
 #endif
       else if (!strcmp (name, "-rs"))
         random_init (atoi (value));
@@ -351,14 +362,21 @@ usage (void)
           "  cat FILE           Print FILE to the console.\n"
           "  rm FILE            Delete FILE.\n"
           "Use these actions indirectly via `pintos' -g and -p options:\n"
-          "  extract            Untar from scratch disk into file system.\n"
-          "  append FILE        Append FILE to tar file on scratch disk.\n"
+          "  extract            Untar from scratch device into file system.\n"
+          "  append FILE        Append FILE to tar file on scratch device.\n"
 #endif
           "\nOptions:\n"
           "  -h                 Print this help message and power off.\n"
           "  -q                 Power off VM after actions or on panic.\n"
           "  -r                 Reboot after actions.\n"
-          "  -f                 Format file system disk during startup.\n"
+#ifdef FILESYS
+          "  -f                 Format file system device during startup.\n"
+          "  -filesys=BDEV      Use BDEV for file system instead of default.\n"
+          "  -scratch=BDEV      Use BDEV for scratch instead of default.\n"
+#ifdef VM
+          "  -swap=BDEV         Use BDEV for swap instead of default.\n"
+#endif
+#endif
           "  -rs=SEED           Set random number seed to SEED.\n"
           "  -mlfqs             Use multi-level feedback queue scheduler.\n"
 #ifdef USERPROG
@@ -367,3 +385,45 @@ usage (void)
           );
   shutdown_power_off ();
 }
+
+#ifdef FILESYS
+/* Figure out what block devices to cast in the various Pintos roles. */
+static void
+locate_block_devices (void)
+{
+  locate_block_device (BLOCK_FILESYS, filesys_bdev_name);
+  locate_block_device (BLOCK_SCRATCH, scratch_bdev_name);
+#ifdef VM
+  locate_block_device (BLOCK_SWAP, swap_bdev_name);
+#endif
+}
+
+/* Figures out what block device to use for the given ROLE: the
+   block device with the given NAME, if NAME is non-null,
+   otherwise the first block device in probe order of type
+   ROLE. */
+static void
+locate_block_device (enum block_type role, const char *name)
+{
+  struct block *block = NULL;
+
+  if (name != NULL)
+    {
+      block = block_get_by_name (name);
+      if (block == NULL)
+        PANIC ("No such block device \"%s\"", name);
+    }
+  else
+    {
+      for (block = block_first (); block != NULL; block = block_next (block))
+        if (block_type (block) == role)
+          break;
+    }
+
+  if (block != NULL)
+    {
+      printf ("%s: using %s\n", block_type_name (role), block_name (block));
+      block_set_role (role, block);
+    }
+}
+#endif
index da6cc04c81dfbfeec3c5dd007029a4caa147fab7..8a3df903481dacab972dbe1a8a31b49a2f04f739 100644 (file)
@@ -6,9 +6,6 @@
 #include <stddef.h>
 #include <stdint.h>
 
-/* Physical memory size, in 4 kB pages. */
-extern size_t init_ram_pages;
-
 /* Page directory with kernel mappings only. */
 extern uint32_t *init_page_dir;
 
index 6154d08dd93bec35c0e2db890b5766e81484ec58..19082d5d3896111dae63de572d7ef5649e8220d3 100644 (file)
@@ -5,17 +5,19 @@ OUTPUT_ARCH("i386")
 ENTRY(start)                   /* Kernel starts at "start" symbol. */
 SECTIONS
 {
-  /* Specifies the virtual address for the kernel base. */
-  . = LOADER_PHYS_BASE + LOADER_KERN_BASE;
+  /* Specify the kernel base address. */
+  _start = LOADER_PHYS_BASE + LOADER_KERN_BASE;
 
-  _start = .;
+  /* Make room for the ELF headers. */
+  . = _start + SIZEOF_HEADERS;
 
   /* Kernel starts with code, followed by read-only data and writable data. */
   .text : { *(.start) *(.text) } = 0x90
   .rodata : { *(.rodata) *(.rodata.*) 
              . = ALIGN(0x1000); 
              _end_kernel_text = .; }
-  .data : { *(.data) }
+  .data : { *(.data) 
+           _signature = .; LONG(0xaa55aa55) }
 
   /* BSS (zero-initialized data) is after everything else. */
   _start_bss = .;
@@ -23,4 +25,6 @@ SECTIONS
   _end_bss = .;
 
   _end = .;
+
+  ASSERT (_end - _start <= 512K, "Kernel image is too big.")
 }
index b7842d3840743c6f25ab0bceaafcbd97e74e65fd..a5819dd28429080f7e6e5f7b70dd277579d12950 100644 (file)
-/* This file is derived from source code used in MIT's 6.828
-   course.  The original copyright notice is reproduced in full
-   below. */
-
-/*
- * Copyright (C) 1997 Massachusetts Institute of Technology 
- *
- * This software is being provided by the copyright holders under the
- * following license. By obtaining, using and/or copying this software,
- * you agree that you have read, understood, and will comply with the
- * following terms and conditions:
- *
- * Permission to use, copy, modify, distribute, and sell this software
- * and its documentation for any purpose and without fee or royalty is
- * hereby granted, provided that the full text of this NOTICE appears on
- * ALL copies of the software and documentation or portions thereof,
- * including modifications, that you make.
- *
- * THIS SOFTWARE IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO
- * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE,
- * BUT NOT LIMITATION, COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR
- * WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR
- * THAT THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY
- * THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. COPYRIGHT
- * HOLDERS WILL BEAR NO LIABILITY FOR ANY USE OF THIS SOFTWARE OR
- * DOCUMENTATION.
- *
- * The name and trademarks of copyright holders may NOT be used in
- * advertising or publicity pertaining to the software without specific,
- * written prior permission. Title to copyright in this software and any
- * associated documentation will at all times remain with copyright
- * holders. See the file AUTHORS which should have accompanied this software
- * for a list of all copyright holders.
- *
- * This file may be derived from previously copyrighted software. This
- * copyright applies only to those changes made by the copyright
- * holders listed in the AUTHORS file. The rest of this file is covered by
- * the copyright notices, if any, listed below.
- */
-
 #include "threads/loader.h"
-       
+
 #### Kernel loader.
 
-#### This code should be stored in the first sector of the hard disk.
+#### This code should be stored in the first sector of a hard disk.
 #### When the BIOS runs, it loads this code at physical address
-#### 0x7c00-0x7e00 (512 bytes).  Then it jumps to the beginning of it,
-#### in real mode.  This code switches into protected mode (32-bit
-#### mode) so that all of memory can accessed, loads the kernel into
-#### memory, and jumps to the first byte of the kernel, where start.S
-#### is linked.
-       
-/* Flags in control register 0. */
-#define CR0_PE 0x00000001      /* Protection Enable. */
-#define CR0_EM 0x00000004      /* (Floating-point) Emulation. */
-#define CR0_PG 0x80000000      /* Paging. */
-#define CR0_WP 0x00010000      /* Write-Protect enable in kernel mode. */
-
-
-.globl start
-start:
-       
-# Code runs in real mode, which is a 16-bit segment.
+#### 0x7c00-0x7e00 (512 bytes) and jumps to the beginning of it,
+#### in real mode.  The loader loads the kernel into memory and jumps
+#### to its entry point, which is the start function in start.S.
+####
+#### The BIOS passes in the drive that the loader was read from as
+#### DL, with floppy drives numbered 0x00, 0x01, ... and hard drives
+#### numbered 0x80, 0x81, ...  We want to support booting a kernel on
+#### a different drive from the loader, so we don't take advantage of
+#### this.
+
+# Runs in real mode, which is a 16-bit segment.
        .code16
 
-# Disable interrupts, because we will not be prepared to handle them
-# in protected mode until much later.
-# String instructions go upward (e.g. for "rep stosl" below).
-
-       cli
-       cld
-
-# Set up data segments.
-
-       subw %ax, %ax
-       movw %ax, %es
-       movw %ax, %ds
-
-# Set up stack segment.
-# Stack grows downward starting from us.
-# We don't ever use the stack, but we call into the BIOS,
-# which might.
-
-       movw %ax, %ss
-       movw $0x7c00, %sp
-       
-#### Enable A20.  Address line 20 is tied to low when the machine
-#### boots, which prevents addressing memory about 1 MB.  This code
-#### fixes it.
-       
-# Poll status register while busy.
-
-1:     inb $0x64, %al
-       testb $0x2, %al
-       jnz 1b
-
-# Send command for writing output port.
-
-       movb $0xd1, %al
-       outb %al, $0x64
-
-# Poll status register while busy.
-
-1:     inb $0x64, %al
-       testb $0x2, %al
-       jnz 1b
-
-# Enable A20 line.
-
-       movb $0xdf, %al
-       outb %al, $0x60
-
-#### Get memory size, via interrupt 15h function 88h.  Returns CF
-#### clear if successful, with AX = (kB of physical memory) - 1024.
-#### This only works for memory sizes <= 65 MB, which should be fine
-#### for our purposes.  We cap memory at 64 MB because that's all we
-#### prepare page tables for, below.
-       
-       movb $0x88, %ah
-       int $0x15
-       jc panic
-       cli                     # BIOS might have enabled interrupts
-       addl $1024, %eax        # Total kB memory
-       cmp $0x10000, %eax      # Cap at 64 MB
+# Set up segment registers.
+# Set stack to grow downward from 60 kB (after boot, the kernel
+# continues to use this stack for its initial thread).
+
+       sub %ax, %ax
+       mov %ax, %ds
+       mov %ax, %ss
+       mov $0xf000, %esp
+
+# Configure serial port so we can report progress without connected VGA.
+# See [IntrList] for details.
+       sub %dx, %dx                    # Serial port 0.
+       mov $0x00e3, %ax                # 9600 bps, N-8-1.
+       int $0x14                       # Destroys AX.
+
+       call puts
+       .string "Pintos loader"
+
+#### Read the partition table on each system hard disk and scan for a
+#### partition of type 0x20, which is the type that we use for a
+#### Pintos kernel.
+####
+#### Read [Partitions] for a description of the partition table format
+#### that we parse.
+####
+#### We print out status messages to show the disk and partition being
+#### scanned, e.g. hda1234 as we scan four partitions on the first
+#### hard disk.
+
+       mov $0x80, %dl                  # Hard disk 0.
+read_mbr:
+       sub %ebx, %ebx                  # Sector 0.
+       mov $0x2000, %ax                # Use 0x20000 for buffer.
+       mov %ax, %es
+       call read_sector
+       jc no_such_drive
+
+       # Print hd[a-z].
+       call puts
+       .string " hd"
+       mov %dl, %al
+       add $'a' - 0x80, %al
+       call putc
+
+       # Check for MBR signature--if not present, it's not a
+       # partitioned hard disk.
+       cmpw $0xaa55, %es:510
+       jne next_drive
+
+       mov $446, %si                   # Offset of partition table entry 1.
+       mov $'1', %al
+check_partition:
+       # Is it an unused partition?
+       cmpl $0, %es:(%si)
+       je next_partition
+
+       # Print [1-4].
+       call putc
+
+       # Is it a Pintos kernel partition?
+       cmpb $0x20, %es:4(%si)
+       jne next_partition
+
+       # Is it a bootable partition?
+       cmpb $0x80, %es:(%si)
+       je load_kernel
+
+next_partition:
+       # No match for this partition, go on to the next one.
+       add $16, %si                    # Offset to next partition table entry.
+       inc %al
+       cmp $510, %si
+       jb check_partition
+
+next_drive:
+       # No match on this drive, go on to the next one.
+       inc %dl
+       jnc read_mbr
+
+no_such_drive:
+no_boot_partition:
+       # Didn't find a Pintos kernel partition anywhere, give up.
+       call puts
+       .string "\rNot found\r"
+
+       # Notify BIOS that boot failed.  See [IntrList].
+       int $0x18
+
+#### We found a kernel.  The kernel's drive is in DL.  The partition
+#### table entry for the kernel's partition is at ES:SI.  Our job now
+#### is to read the kernel from disk and jump to its start address.
+
+load_kernel:
+       call puts
+       .string "\rLoading"
+
+       # Figure out number of sectors to read.  A Pintos kernel is
+       # just an ELF format object, which doesn't have an
+       # easy-to-read field to identify its own size (see [ELF1]).
+       # But we limit Pintos kernels to 512 kB for other reasons, so
+       # it's easy enough to just read the entire contents of the
+       # partition or 512 kB from disk, whichever is smaller.
+       mov %es:12(%si), %ecx           # EBP = number of sectors
+       cmp $1024, %ecx                 # Cap size at 512 kB
        jbe 1f
-       mov $0x10000, %eax
-1:     shrl $2, %eax           # Total 4 kB pages
-       movl %eax, ram_pgs
-       
-#### Create temporary page directory and page table and set page
-#### directory base register.
-
-# Create page directory at 64 kB and fill with zeroes.
-       mov $0x1000, %ax
+       mov $1024, %cx
+1:
+
+       mov %es:8(%si), %ebx            # EBX = first sector
+       mov $0x2000, %ax                # Start load address: 0x20000
+
+next_sector:
+       # Read one sector into memory.
+       mov %ax, %es                    # ES:0000 -> load address
+       call read_sector
+       jc read_failed
+
+       # Print '.' as progress indicator once every 16 sectors == 8 kB.
+       test $15, %bl
+       jnz 1f
+       call puts
+       .string "."
+1:
+
+       # Advance memory pointer and disk sector.
+       add $0x20, %ax
+       inc %bx
+       loop next_sector
+
+       call puts
+       .string "\r"
+
+#### Transfer control to the kernel that we loaded.  We read the start
+#### address out of the ELF header (see [ELF1]) and convert it from a
+#### 32-bit linear address into a 16:16 segment:offset address for
+#### real mode, then jump to the converted address.  The 80x86 doesn't
+#### have an instruction to jump to an absolute segment:offset kept in
+#### registers, so in fact we store the address in a temporary memory
+#### location, then jump indirectly through that location.  To save 4
+#### bytes in the loader, we reuse 4 bytes of the loader's code for
+#### this temporary pointer.
+
+       mov $0x2000, %ax
        mov %ax, %es
-       subl %eax, %eax
-       subl %edi, %edi
-       movl $0x400, %ecx
-       rep stosl
-
-# Add PDEs to point to PTEs for the first 64 MB of RAM.
-# Also add identical PDEs starting at LOADER_PHYS_BASE.
-# See [IA32-v3a] section 3.7.6 "Page-Directory and Page-Table Entries"
-# for a description of the bits in %eax.
-       
-
-       movl $0x11007, %eax
-       movl $0x11, %ecx
-       subl %edi, %edi
-1:     movl %eax, %es:(%di)
-       movl %eax, %es:LOADER_PHYS_BASE >> 20(%di)
-       addw $4, %di
-       addl $0x1000, %eax
-       loop 1b
-
-# Set up one-to-map linear to physical map for the first 64 MB of RAM.
-# See [IA32-v3a] section 3.7.6 "Page-Directory and Page-Table Entries"
-# for a description of the bits in %eax.
-
-       movw $0x1100, %ax
-       movw %ax, %es
-       movl $0x7, %eax
-       movl $0x4000, %ecx
-       subl %edi, %edi
-1:     movl %eax, %es:(%di)
-       addw $4, %di
-       addl $0x1000, %eax
-       loop 1b
-
-# Set page directory base register.
-
-       movl $0x10000, %eax
-       movl %eax, %cr3
-       
-#### Switch to protected mode.
-
-# Note that interrupts are still off.
-
-# Point the GDTR to our GDT.  Protected mode requires a GDT.
-# We need a data32 prefix to ensure that all 32 bits of the GDT
-# descriptor are loaded (default is to load only 24 bits).
-
-       data32 lgdt gdtdesc
-
-# Then we turn on the following bits in CR0:
-#    PE (Protect Enable): this turns on protected mode.
-#    PG (Paging): turns on paging.
-#    WP (Write Protect): if unset, ring 0 code ignores
-#       write-protect bits in page tables (!).
-#    EM (Emulation): forces floating-point instructions to trap.
-#       We don't support floating point. 
-       
-       movl %cr0, %eax
-       orl $CR0_PE | CR0_PG | CR0_WP | CR0_EM, %eax
-       movl %eax, %cr0
-       
-# We're now in protected mode in a 16-bit segment.  The CPU still has
-# the real-mode code segment cached in %cs's segment descriptor.  We
-# need to reload %cs, and the easiest way is to use a far jump.
-# Because we're not in a 32-bit segment the data32 prefix is needed to
-# jump to a 32-bit offset.
-
-       data32 ljmp $SEL_KCSEG, $1f + LOADER_PHYS_BASE
-       
-# We're now in protected mode in a 32-bit segment.
-
-       .code32
-
-# Reload all the other segment registers and the stack pointer to
-# point into our new GDT.
-
-1:     movw $SEL_KDSEG, %ax
-       movw %ax, %ds           
-       movw %ax, %es           
-       movw %ax, %fs           
-       movw %ax, %gs           
-       movw %ax, %ss
-       movl $LOADER_PHYS_BASE + 0x30000, %esp
-
-#### Load kernel starting at physical address LOADER_KERN_BASE by
-#### frobbing the IDE controller directly.
-
-       movl $1, %ebx
-       movl $LOADER_KERN_BASE + LOADER_PHYS_BASE, %edi
-
-# Disable interrupt delivery by IDE controller, because we will be
-# polling for data.
-# (If we don't do this, Bochs 2.2.6 will never deliver any IDE
-# interrupt to us later after we reset the interrupt controller during
-# boot, even if we also reset the IDE controller.)
-
-       movw $0x3f6, %dx
-       movb $0x02, %al
-       outb %al, %dx
-       
-read_sector:
-
-# Poll status register while controller busy.
-
-       movl $0x1f7, %edx
-1:     inb %dx, %al
-       testb $0x80, %al
-       jnz 1b
-
-# Read a single sector.
-
-       movl $0x1f2, %edx
-       movb $1, %al
-       outb %al, %dx
-
-# Sector number to write in low 28 bits.
-# LBA mode, device 0 in top 4 bits.
-
-       movl %ebx, %eax
-       andl $0x0fffffff, %eax
-       orl $0xe0000000, %eax
-
-# Dump %eax to ports 0x1f3...0x1f6.
-
-       movl $4, %ecx
-1:     incw %dx
-       outb %al, %dx
-       shrl $8, %eax
-       loop 1b
-
-# READ command to command register.
-
-       incw %dx
-       movb $0x20, %al
-       outb %al, %dx
-
-# Poll status register while controller busy.
-
-1:     inb %dx, %al
-       testb $0x80, %al
-       jnz 1b
+       mov %es:0x18, %dx
+       mov %dx, start
+       movw $0x2000, start + 2
+       ljmp *start
 
-# Poll status register until data ready.
-
-1:     inb %dx, %al
-       testb $0x08, %al
-       jz 1b
-
-# Transfer sector.
-
-       movl $256, %ecx
-       movl $0x1f0, %edx
-       rep insw
-
-# Next sector.
-
-       incl %ebx
-       cmpl $KERNEL_LOAD_PAGES*8 + 1, %ebx
-       jnz read_sector
-
-#### Jump to kernel entry point.
-
-       movl $LOADER_PHYS_BASE + LOADER_KERN_BASE, %eax
-       call *%eax
-       jmp panic
-
-#### GDT
-
-gdt:
-       .quad 0x0000000000000000        # null seg
-       .quad 0x00cf9a000000ffff        # code seg
-       .quad 0x00cf92000000ffff        # data seg
-       
-gdtdesc:
-       .word   0x17                    # sizeof (gdt) - 1
-       .long   gdt + LOADER_PHYS_BASE  # address gdt
-
-#### Fatal error.
-#### Print panic_message (with help from the BIOS) and spin.
-
-panic:  .code16                        # We only panic in real mode.
-       movw $panic_message, %si
-       movb $0xe, %ah
-       subb %bh, %bh
-1:     lodsb
+read_failed:
+start:
+       # Disk sector read failed.
+       call puts
+1:     .string "\rBad read\r"
+
+       # Notify BIOS that boot failed.  See [IntrList].
+       int $0x18
+
+#### Print string subroutine.  To save space in the loader, this
+#### subroutine takes its null-terminated string argument from the
+#### code stream just after the call, and then returns to the byte
+#### just after the terminating null.  This subroutine preserves all
+#### general-purpose registers.
+
+puts:  xchg %si, %ss:(%esp)
+       push %ax
+next_char:
+       mov %cs:(%si), %al
+       inc %si
        test %al, %al
-2:     jz 2b                   # Spin.
+       jz 1f
+       call putc
+       jmp next_char
+1:     pop %ax
+       xchg %si, %ss:(%esp)
+       ret
+
+#### Character output subroutine.  Prints the character in AL to the
+#### VGA display and serial port 0, using BIOS services (see
+#### [IntrList]).  Preserves all general-purpose registers.
+####
+#### If called upon to output a carriage return, this subroutine
+#### automatically supplies the following line feed.
+
+putc:  pusha
+
+1:     sub %bh, %bh                    # Page 0.
+       mov $0x0e, %ah                  # Teletype output service.
        int $0x10
+
+       mov $0x01, %ah                  # Serial port output service.
+       sub %dx, %dx                    # Serial port 0.
+       int $0x14                       # Destroys AH.
+
+       cmp $'\r', %al
+       jne popa_ret
+       mov $'\n', %al
        jmp 1b
 
-panic_message:
-       .ascii "Panic!"
-       .byte 0
+#### Sector read subroutine.  Takes a drive number in DL (0x80 = hard
+#### disk 0, 0x81 = hard disk 1, ...) and a sector number in EBX, and
+#### reads the specified sector into memory at ES:0000.  Returns with
+#### carry set on error, clear otherwise.  Preserves all
+#### general-purpose registers.
 
-#### Physical memory size in 4 kB pages.
-#### This is initialized by the loader and read by the kernel.
-       .org LOADER_RAM_PGS - LOADER_BASE
-ram_pgs:
-       .long 0
+read_sector:
+       pusha
+       sub %eax, %eax
+       push %eax                       # LBA sector number [32:63]
+       push %ebx                       # LBA sector number [0:31]
+       push %es                        # Buffer segment
+       push %ax                        # Buffer offset (always 0)
+       push $1                         # Number of sectors to read
+       push $16                        # Packet size
+       mov $0x42, %ah                  # Extended read
+       mov %sp, %si                    # DS:SI -> packet
+       int $0x13                       # Error code in CF
+       popa                            # Pop 16 bytes, preserve flags
+popa_ret:
+       popa
+       ret                             # Error code still in CF
 
 #### Command-line arguments and their count.
 #### This is written by the `pintos' utility and read by the kernel.
 #### The loader itself does not do anything with the command line.
        .org LOADER_ARG_CNT - LOADER_BASE
-arg_cnt:
-       .long 0
+       .fill LOADER_ARG_CNT_LEN, 1, 0
+
        .org LOADER_ARGS - LOADER_BASE
-args:
-       .fill 0x80, 1, 0
+       .fill LOADER_ARGS_LEN, 1, 0
+
+#### Partition table.
+       .org LOADER_PARTS - LOADER_BASE
+       .fill LOADER_PARTS_LEN, 1, 0
 
-#### Boot-sector signature.
-#### The BIOS checks that this is set properly.
+#### Boot-sector signature for BIOS inspection.
        .org LOADER_SIG - LOADER_BASE
        .word 0xaa55
index 5bd981384202504e528fa968edd25814679228a0..1bfe111185d0f777f5cadf83824d5abe03b64b00 100644 (file)
@@ -6,28 +6,23 @@
 #define LOADER_END  0x7e00      /* Physical address of end of loader. */
 
 /* Physical address of kernel base. */
-#define LOADER_KERN_BASE 0x100000       /* 1 MB. */
+#define LOADER_KERN_BASE 0x20000       /* 128 kB. */
 
 /* Kernel virtual address at which all physical memory is mapped.
-
-   The loader maps the 4 MB at the bottom of physical memory to
-   this virtual base address.  Later, paging_init() adds the rest
-   of physical memory to the mapping.
-
-   This must be aligned on a 4 MB boundary. */
+   Must be aligned on a 4 MB boundary. */
 #define LOADER_PHYS_BASE 0xc0000000     /* 3 GB. */
 
 /* Important loader physical addresses. */
 #define LOADER_SIG (LOADER_END - LOADER_SIG_LEN)   /* 0xaa55 BIOS signature. */
-#define LOADER_ARGS (LOADER_SIG - LOADER_ARGS_LEN)     /* Command-line args. */
+#define LOADER_PARTS (LOADER_SIG - LOADER_PARTS_LEN)     /* Partition table. */
+#define LOADER_ARGS (LOADER_PARTS - LOADER_ARGS_LEN)   /* Command-line args. */
 #define LOADER_ARG_CNT (LOADER_ARGS - LOADER_ARG_CNT_LEN) /* Number of args. */
-#define LOADER_RAM_PGS (LOADER_ARG_CNT - LOADER_RAM_PGS_LEN) /* # RAM pages. */
 
 /* Sizes of loader data structures. */
 #define LOADER_SIG_LEN 2
+#define LOADER_PARTS_LEN 64
 #define LOADER_ARGS_LEN 128
 #define LOADER_ARG_CNT_LEN 4
-#define LOADER_RAM_PGS_LEN 4
 
 /* GDT selectors defined by loader.
    More selectors are defined by userprog/gdt.h. */
 #define SEL_KCSEG       0x08    /* Kernel code selector. */
 #define SEL_KDSEG       0x10    /* Kernel data selector. */
 
+#ifndef __ASSEMBLER__
+#include <stdint.h>
+
+/* Amount of physical memory, in 4 kB pages. */
+extern uint32_t init_ram_pages;
+#endif
+
 #endif /* threads/loader.h */
index 177001f3c1c4b4ae622422be84f356217ac6dcf5..4fc8394c8f078a20c5a48722c36e3e022658620e 100644 (file)
@@ -7,7 +7,6 @@
 #include <stdint.h>
 #include <stdio.h>
 #include <string.h>
-#include "threads/init.h"
 #include "threads/loader.h"
 #include "threads/synch.h"
 #include "threads/vaddr.h"
@@ -46,12 +45,8 @@ static bool page_from_pool (const struct pool *, void *page);
 void
 palloc_init (size_t user_page_limit)
 {
-  /* End of the kernel as recorded by the linker.
-     See kernel.lds.S. */
-  extern char _end;
-
-  /* Free memory. */
-  uint8_t *free_start = pg_round_up (&_end);
+  /* Free memory starts at 1 MB and runs to the end of RAM. */
+  uint8_t *free_start = ptov (1024 * 1024);
   uint8_t *free_end = ptov (init_ram_pages * PGSIZE);
   size_t free_pages = (free_end - free_start) / PGSIZE;
   size_t user_pages = free_pages / 2;
index 68c604cc276b33a6701dab250d16784de3d45bd0..29ffa7a42f3ed45a948befce9bceb9bd3c518ff5 100644 (file)
-#### The loader needs to have some way to know the kernel's entry
-#### point, that is, the address to which it should jump to start the
-#### kernel.  We handle this by writing the linker script kernel.lds.S
-#### so that this module appears at the very beginning of the kernel
-#### image, and then using that as the entry point.
-
-.section .start
-       
-.globl start
+       #include "threads/loader.h"
+
+#### Kernel startup code.
+
+#### The loader (in loader.S) loads the kernel at physical address
+#### 0x20000 (128 kB) and jumps to "start", defined here.  This code
+#### switches from real mode to 32-bit protected mode and calls
+#### main().
+
+/* Flags in control register 0. */
+#define CR0_PE 0x00000001      /* Protection Enable. */
+#define CR0_EM 0x00000004      /* (Floating-point) Emulation. */
+#define CR0_PG 0x80000000      /* Paging. */
+#define CR0_WP 0x00010000      /* Write-Protect enable in kernel mode. */
+
+       .section .start
+
+# The following code runs in real mode, which is a 16-bit code segment.
+       .code16
+
 .func start
-       # Terminate the backtrace that debug_backtrace() would output.
-       movl $0, %ebp
+.globl start
+start:
+
+# The loader called into us with CS = 0x2000, SS = 0x0000, ESP = 0xf000,
+# but we should initialize the other segment registers.
+
+       mov $0x2000, %ax
+       mov %ax, %ds
+       mov %ax, %es
+
+# Set string instructions to go upward.
+       cld
+
+#### Get memory size, via interrupt 15h function 88h (see [IntrList]),
+#### which returns AX = (kB of physical memory) - 1024.  This only
+#### works for memory sizes <= 65 MB, which should be fine for our
+#### purposes.  We cap memory at 64 MB because that's all we prepare
+#### page tables for, below.
+
+       movb $0x88, %ah
+       int $0x15
+       addl $1024, %eax        # Total kB memory
+       cmp $0x10000, %eax      # Cap at 64 MB
+       jbe 1f
+       mov $0x10000, %eax
+1:     shrl $2, %eax           # Total 4 kB pages
+       addr32 movl %eax, init_ram_pages - LOADER_PHYS_BASE - 0x20000
+
+#### Enable A20.  Address line 20 is tied low when the machine boots,
+#### which prevents addressing memory about 1 MB.  This code fixes it.
+
+# Poll status register while busy.
+
+1:     inb $0x64, %al
+       testb $0x2, %al
+       jnz 1b
+
+# Send command for writing output port.
+
+       movb $0xd1, %al
+       outb %al, $0x64
+
+# Poll status register while busy.
+
+1:     inb $0x64, %al
+       testb $0x2, %al
+       jnz 1b
+
+# Enable A20 line.
+
+       movb $0xdf, %al
+       outb %al, $0x60
+
+# Poll status register while busy.
+
+1:     inb $0x64, %al
+       testb $0x2, %al
+       jnz 1b
+
+#### Create temporary page directory and page table and set page
+#### directory base register.
+
+# Create page directory at 0xf000 (60 kB) and fill with zeroes.
+       mov $0xf00, %ax
+       mov %ax, %es
+       subl %eax, %eax
+       subl %edi, %edi
+       movl $0x400, %ecx
+       rep stosl
 
-       # Call main.
-start: call main
+# Add PDEs to point to page tables for the first 64 MB of RAM.
+# Also add identical PDEs starting at LOADER_PHYS_BASE.
+# See [IA32-v3a] section 3.7.6 "Page-Directory and Page-Table Entries"
+# for a description of the bits in %eax.
+
+       movl $0x10007, %eax
+       movl $0x11, %ecx
+       subl %edi, %edi
+1:     movl %eax, %es:(%di)
+       movl %eax, %es:LOADER_PHYS_BASE >> 20(%di)
+       addw $4, %di
+       addl $0x1000, %eax
+       loop 1b
+
+# Set up page tables for one-to-map linear to physical map for the
+# first 64 MB of RAM.
+# See [IA32-v3a] section 3.7.6 "Page-Directory and Page-Table Entries"
+# for a description of the bits in %eax.
+
+       movw $0x1000, %ax
+       movw %ax, %es
+       movl $0x7, %eax
+       movl $0x4000, %ecx
+       subl %edi, %edi
+1:     movl %eax, %es:(%di)
+       addw $4, %di
+       addl $0x1000, %eax
+       loop 1b
+
+# Set page directory base register.
+
+       movl $0xf000, %eax
+       movl %eax, %cr3
+
+#### Switch to protected mode.
+
+# First, disable interrupts.  We won't set up the IDT until we get
+# into C code, so any interrupt would blow us away.
+
+       cli
+
+# Protected mode requires a GDT, so point the GDTR to our GDT.
+# We need a data32 prefix to ensure that all 32 bits of the GDT
+# descriptor are loaded (default is to load only 24 bits).
+# The CPU doesn't need an addr32 prefix but ELF doesn't do 16-bit
+# relocations.
+
+       data32 addr32 lgdt gdtdesc - LOADER_PHYS_BASE - 0x20000
+
+# Then we turn on the following bits in CR0:
+#    PE (Protect Enable): this turns on protected mode.
+#    PG (Paging): turns on paging.
+#    WP (Write Protect): if unset, ring 0 code ignores
+#       write-protect bits in page tables (!).
+#    EM (Emulation): forces floating-point instructions to trap.
+#       We don't support floating point.
+
+       movl %cr0, %eax
+       orl $CR0_PE | CR0_PG | CR0_WP | CR0_EM, %eax
+       movl %eax, %cr0
+
+# We're now in protected mode in a 16-bit segment.  The CPU still has
+# the real-mode code segment cached in %cs's segment descriptor.  We
+# need to reload %cs, and the easiest way is to use a far jump.
+# Because we're not running in a 32-bit segment the data32 prefix is
+# needed to jump to a 32-bit offset in the target segment.
+
+       data32 ljmp $SEL_KCSEG, $1f
+
+# We're now in protected mode in a 32-bit segment.
+# Let the assembler know.
+
+       .code32
+
+# Reload all the other segment registers and the stack pointer to
+# point into our new GDT.
+
+1:     mov $SEL_KDSEG, %ax
+       mov %ax, %ds
+       mov %ax, %es
+       mov %ax, %fs
+       mov %ax, %gs
+       mov %ax, %ss
+       addl $LOADER_PHYS_BASE, %esp
+       movl $0, %ebp                   # Null-terminate main()'s backtrace
+
+#### Call main().
+
+       call main
+
+# main() shouldn't ever return.  If it does, spin.
 
-       # main() should not return, but if it does, spin.
 1:     jmp 1b
 .endfunc
+
+#### GDT
+
+       .align 8
+gdt:
+       .quad 0x0000000000000000        # Null segment.  Not used by CPU.
+       .quad 0x00cf9a000000ffff        # System code, base 0, limit 4 GB.
+       .quad 0x00cf92000000ffff        # System data, base 0, limit 4 GB.
+
+gdtdesc:
+       .word   gdtdesc - gdt - 1       # Size of the GDT, minus 1 byte.
+       .long   gdt                     # Address of the GDT.
+
+#### Physical memory size in 4 kB pages.  This is exported to the rest
+#### of the kernel.
+.globl init_ram_pages
+init_ram_pages:
+       .long 0
+
index 9db3cfa6c91baa76b1557ccfd420a2b037bd0f9e..e4dbb087c332564db792a930c06dfbac6d2d4eb4 100644 (file)
@@ -1,6 +1,6 @@
 # -*- makefile -*-
 
-os.dsk: DEFINES = -DUSERPROG -DFILESYS
+kernel.bin: DEFINES = -DUSERPROG -DFILESYS
 KERNEL_SUBDIRS = threads devices lib lib/kernel userprog filesys
 TEST_SUBDIRS = tests/userprog tests/userprog/no-vm tests/filesys/base
 GRADING_FILE = $(SRCDIR)/tests/userprog/Grading
diff --git a/src/utils/Pintos.pm b/src/utils/Pintos.pm
new file mode 100644 (file)
index 0000000..70df40d
--- /dev/null
@@ -0,0 +1,491 @@
+# Pintos helper subroutines.
+
+# Number of bytes available for the loader at the beginning of the MBR.
+# Kernel command-line arguments follow the loader.
+our $LOADER_SIZE = 314;
+
+# Partition types.
+my (%role2type) = (KERNEL => 0x20,
+                  FILESYS => 0x21,
+                  SCRATCH => 0x22,
+                  SWAP => 0x23);
+my (%type2role) = reverse %role2type;
+
+# Order of roles within a given disk.
+our (@role_order) = qw (KERNEL FILESYS SCRATCH SWAP);
+
+# Partitions.
+#
+# Valid keys are KERNEL, FILESYS, SCRATCH, SWAP.  Only those
+# partitions which are in use are included.
+#
+# Each value is a reference to a hash.  If the partition's contents
+# are to be obtained from a file (that will be copied into a new
+# virtual disk), then the hash contains:
+#
+# FILE => name of file from which the partition's contents are copied
+#         (perhaps "/dev/zero"),
+# OFFSET => offset in bytes in FILE,
+# BYTES => size in bytes of contents from FILE,
+#
+# If the partition is taken from a virtual disk directly, then it
+# contains the following.  The same keys are also filled in once a
+# file-based partition has been copied into a new virtual disk:
+#
+# DISK => name of virtual disk file,
+# START => sector offset of start of partition within DISK,
+# SECTORS => number of sectors of partition within DISK, which is usually
+#            greater than round_up (BYTES, 512) due to padding.
+our (%parts);
+
+# set_part($opt, $arg)
+#
+# For use as a helper function for Getopt::Long::GetOptions to set
+# disk sources.
+sub set_part {
+    my ($opt, $arg) = @_;
+    my ($role, $source) = $opt =~ /^([a-z]+)(?:-([a-z]+))?/ or die;
+
+    $role = uc $role;
+    $source = 'FILE' if $source eq '';
+
+    die "can't have two sources for \L$role\E partition"
+      if exists $parts{$role};
+
+    do_set_part ($role, $source, $arg);
+}
+
+# do_set_part($role, $source, $arg)
+#
+# Sets partition $role as coming from $source (one of 'file', 'from',
+# or 'size').  $arg is a file name for 'file' or 'from', a size in
+# megabytes for 'size'.
+sub do_set_part {
+    my ($role, $source, $arg) = @_;
+
+    my ($p) = $parts{$role} = {};
+    if ($source eq 'file') {
+       if (read_mbr ($arg)) {
+           print STDERR "warning: $arg looks like a partitioned disk ";
+           print STDERR "(did you want --$role-from=$arg or --disk=$arg?)\n"
+       }
+
+       $p->{FILE} = $arg;
+       $p->{OFFSET} = 0;
+       $p->{BYTES} = -s $arg;
+    } elsif ($source eq 'from') {
+       my (%pt) = read_partition_table ($arg);
+       my ($sp) = $pt{$role};
+       die "$arg: does not contain \L$role\E partition\n" if !defined $sp;
+
+       $p->{FILE} = $arg;
+       $p->{OFFSET} = $sp->{START} * 512;
+       $p->{BYTES} = $sp->{SECTORS} * 512;
+    } elsif ($source eq 'size') {
+       $arg =~ /^\d+(\.\d+)?|\.\d+$/ or die "$arg: not a valid size in MB\n";
+
+       $p->{FILE} = "/dev/zero";
+       $p->{OFFSET} = 0;
+       $p->{BYTES} = ceil ($arg * 1024 * 1024);
+    } else {
+       die;
+    }
+}
+
+# set_geometry('HEADS,SPT')
+# set_geometry('zip')
+#
+# For use as a helper function for Getopt::Long::GetOptions to set
+# disk geometry.
+sub set_geometry {
+    local ($_) = $_[1];
+    if ($_ eq 'zip') {
+       @geometry{'H', 'S'} = (64, 32);
+    } else {
+       @geometry{'H', 'S'} = /^(\d+)[,\s]+(\d+)$/
+         or die "bad syntax for geometry\n";
+       $geometry{H} <= 255 or die "heads limited to 255\n";
+       $geometry{S} <= 63 or die "sectors per track limited to 63\n";
+    }
+}
+
+# set_align('bochs|full|none')
+#
+# For use as a helper function for Getopt::Long::GetOptions to set
+# partition alignment.
+sub set_align {
+    $align = $_[1];
+    die "unknown alignment type \"$align\"\n"
+      if $align ne 'bochs' && $align ne 'full' && $align ne 'none';
+}
+
+# assemble_disk(%args)
+#
+# Creates a virtual disk $args{DISK} containing the partitions
+# described by @args{KERNEL, FILESYS, SCRATCH, SWAP}.
+#
+# Required arguments:
+#   DISK => output disk file name
+#   HANDLE => output file handle (will be closed)
+#
+# Normally at least one of the following is included:
+#   KERNEL, FILESYS, SCRATCH, SWAP => {input:
+#                                     FILE => file to read,
+#                                      OFFSET => byte offset in file,
+#                                      BYTES => byte count from file,
+#
+#                                      output:
+#                                     DISK => output disk file name,
+#                                      START => sector offset in DISK,
+#                                      SECTORS => sector count in DISK},
+#
+# Optional arguments:
+#   ALIGN => 'bochs' (default), 'full', or 'none'
+#   GEOMETRY => {H => heads, S => sectors per track} (default 16, 63)
+#   FORMAT => 'partitioned' (default) or 'raw'
+#   LOADER => $LOADER_SIZE-byte string containing the loader binary
+#   ARGS => ['arg 1', 'arg 2', ...]
+sub assemble_disk {
+    my (%args) = @_;
+
+    my (%geometry) = $args{GEOMETRY} || (H => 16, S => 63);
+
+    my ($align);       # Align partition start, end to cylinder boundary?
+    my ($pad);         # Pad end of disk out to cylinder boundary?
+    if (!defined ($args{ALIGN}) || $args{ALIGN} eq 'bochs') {
+       $align = 0;
+       $pad = 1;
+    } elsif ($args{ALIGN} eq 'full') {
+       $align = 1;
+       $pad = 0;
+    } elsif ($args{ALIGN} eq 'none') {
+       $align = $pad = 0;
+    } else {
+       die;
+    }
+
+    my ($format) = $args{FORMAT} || 'partitioned';
+    die if $format ne 'partitioned' && $format ne 'raw';
+
+    # Check that we have apartitions to copy in.
+    my $part_cnt = grep (defined ($args{$_}), keys %role2type);
+    die "must have exactly one partition for raw output\n"
+      if $format eq 'raw' && $part_cnt != 1;
+
+    # Calculate the disk size.
+    my ($total_sectors) = 0;
+    if ($format eq 'partitioned') {
+       $total_sectors += $align ? $geometry{S} : 1;
+    }
+    for my $role (@role_order) {
+       my ($p) = $args{$role};
+       next if !defined $p;
+
+       die if $p->{DISK};
+
+       my ($bytes) = $p->{BYTES};
+       my ($start) = $total_sectors;
+       my ($end) = $start + div_round_up ($bytes, 512);
+       $end = round_up ($end, cyl_sectors (%geometry)) if $align;
+
+       $p->{DISK} = $args{DISK};
+       $p->{START} = $start;
+       $p->{SECTORS} = $end - $start;
+       $total_sectors = $end;
+    }
+
+    # Write the disk.
+    my ($disk_fn) = $args{DISK};
+    my ($disk) = $args{HANDLE};
+    if ($format eq 'partitioned') {
+       # Pack loader into MBR.
+       my ($loader) = $args{LOADER} || "\xcd\x18";
+       my ($mbr) = pack ("a$LOADER_SIZE", $loader);
+
+       $mbr .= make_kernel_command_line (@{$args{ARGS}});
+
+       # Pack partition table into MBR.
+       $mbr .= make_partition_table (\%geometry, \%args);
+
+       # Add signature to MBR.
+       $mbr .= pack ("v", 0xaa55);
+
+       die if length ($mbr) != 512;
+       write_fully ($disk, $disk_fn, $mbr);
+       write_zeros ($disk, $disk_fn, 512 * ($geometry{S} - 1)) if $align;
+    }
+    for my $role (@role_order) {
+       my ($p) = $args{$role};
+       next if !defined $p;
+
+       my ($source);
+       my ($fn) = $p->{FILE};
+       open ($source, '<', $fn) or die "$fn: open: $!\n";
+       if ($p->{OFFSET}) {
+           sysseek ($source, $p->{OFFSET}, 0) == $p->{OFFSET}
+             or die "$fn: seek: $!\n";
+       }
+       copy_file ($source, $fn, $disk, $disk_fn, $p->{BYTES});
+       close ($source) or die "$fn: close: $!\n";
+
+       write_zeros ($disk, $disk_fn, $p->{SECTORS} * 512 - $p->{BYTES});
+    }
+    if ($pad) {
+       my ($pad_sectors) = round_up ($total_sectors, cyl_sectors (%geometry));
+       write_zeros ($disk, $disk_fn, ($pad_sectors - $total_sectors) * 512);
+    }
+    close ($disk) or die "$disk: close: $!\n";
+}
+
+# make_partition_table({H => heads, S => sectors}, {KERNEL => ..., ...})
+#
+# Creates and returns a partition table for the given partitions and
+# disk geometry.
+sub make_partition_table {
+    my ($geometry, $partitions) = @_;
+    my ($table) = '';
+    for my $role (@role_order) {
+       defined (my $p = $partitions->{$role}) or next;
+
+       my $end = $p->{START} + $p->{SECTORS} - 1;
+       my $bootable = $role eq 'KERNEL';
+
+       $table .= pack ("C", $bootable ? 0x80 : 0);   # Bootable?
+       $table .= pack_chs ($p->{START}, $geometry);  # CHS of partition start
+       $table .= pack ("C", $role2type{$role});      # Partition type
+       $table .= pack_chs($end, $geometry);          # CHS of partition end
+       $table .= pack ("V", $p->{START});            # LBA of partition start
+       $table .= pack ("V", $p->{SECTORS});          # Length in sectors
+       die if length ($table) % 16;
+    }
+    return pack ("a64", $table);
+}
+
+# make_kernel_command_line(@args)
+#
+# Returns the raw bytes to write to an MBR at offset $LOADER_SIZE to
+# set a Pintos kernel command line.
+sub make_kernel_command_line {
+    my (@args) = @_;
+    my ($args) = join ('', map ("$_\0", @args));
+    die "command line exceeds 128 bytes" if length ($args) > 128;
+    return pack ("V a128", scalar (@args), $args);
+}
+
+# copy_file($from_handle, $from_file_name, $to_handle, $to_file_name, $size)
+#
+# Copies $size bytes from $from_handle to $to_handle.
+# $from_file_name and $to_file_name are used in error messages.
+sub copy_file {
+    my ($from_handle, $from_file_name, $to_handle, $to_file_name, $size) = @_;
+
+    while ($size > 0) {
+       my ($chunk_size) = 4096;
+       $chunk_size = $size if $chunk_size > $size;
+       $size -= $chunk_size;
+
+       my ($data) = read_fully ($from_handle, $from_file_name, $chunk_size);
+       write_fully ($to_handle, $to_file_name, $data);
+    }
+}
+
+# read_fully($handle, $file_name, $bytes)
+#
+# Reads exactly $bytes bytes from $handle and returns the data read.
+# $file_name is used in error messages.
+sub read_fully {
+    my ($handle, $file_name, $bytes) = @_;
+    my ($data);
+    my ($read_bytes) = sysread ($handle, $data, $bytes);
+    die "$file_name: read: $!\n" if !defined $read_bytes;
+    die "$file_name: unexpected end of file\n" if $read_bytes != $bytes;
+    return $data;
+}
+
+# write_fully($handle, $file_name, $data)
+#
+# Write $data to $handle.
+# $file_name is used in error messages.
+sub write_fully {
+    my ($handle, $file_name, $data) = @_;
+    my ($written_bytes) = syswrite ($handle, $data);
+    die "$file_name: write: $!\n" if !defined $written_bytes;
+    die "$file_name: short write\n" if $written_bytes != length $data;
+}
+
+sub write_zeros {
+    my ($handle, $file_name, $size) = @_;
+
+    while ($size > 0) {
+       my ($chunk_size) = 4096;
+       $chunk_size = $size if $chunk_size > $size;
+       $size -= $chunk_size;
+
+       write_fully ($handle, $file_name, "\0" x $chunk_size);
+    }
+}
+
+# div_round_up($x,$y)
+#
+# Returns $x / $y, rounded up to the nearest integer.
+# $y must be an integer.
+sub div_round_up {
+    my ($x, $y) = @_;
+    return int ((ceil ($x) + $y - 1) / $y);
+}
+
+# round_up($x, $y)
+#
+# Returns $x rounded up to the nearest multiple of $y.
+# $y must be an integer.
+sub round_up {
+    my ($x, $y) = @_;
+    return div_round_up ($x, $y) * $y;
+}
+
+# cyl_sectors(H => heads, S => sectors)
+#
+# Returns the number of sectors in a cylinder of a disk with the given
+# geometry.
+sub cyl_sectors {
+    my (%geometry) = @_;
+    return $geometry{H} * $geometry{S};
+}
+
+# read_loader($file_name)
+#
+# Reads and returns the first $LOADER_SIZE bytes in $file_name.
+# If $file_name is undefined, tries to find the default loader.
+# Makes sure that the loader is a reasonable size.
+sub read_loader {
+    my ($name) = @_;
+    $name = find_file ("loader.bin") if !defined $name;
+    die "Cannot find loader\n" if !defined $name;
+
+    my ($handle);
+    open ($handle, '<', $name) or die "$name: open: $!\n";
+    -s $handle == $LOADER_SIZE || -s $handle == 512
+      or die "$name: must be exactly $LOADER_SIZE or 512 bytes long\n";
+    $loader = read_fully ($handle, $name, $LOADER_SIZE);
+    close ($handle) or die "$name: close: $!\n";
+    return $loader;
+}
+
+# pack_chs($lba, {H => heads, S => sectors})
+#
+# Converts logical sector $lba to a 3-byte packed geometrical sector
+# in the format used in PC partition tables (see [Partitions]) and
+# returns the geometrical sector as a 3-byte string.
+sub pack_chs {
+    my ($lba, $geometry) = @_;
+    my ($cyl, $head, $sect) = lba_to_chs ($lba, $geometry);
+    return pack ("CCC", $head, $sect | (($cyl >> 2) & 0xc0), $cyl & 0xff);
+}
+
+# lba_to_chs($lba, {H => heads, S => sectors})
+#
+# Returns the geometrical sector corresponding to logical sector $lba
+# given the specified geometry.
+sub lba_to_chs {
+    my ($lba, $geometry) = @_;
+    my ($hpc) = $geometry->{H};
+    my ($spt) = $geometry->{S};
+
+    # Source:
+    # http://en.wikipedia.org/wiki/CHS_conversion
+    use integer;
+    my $cyl = $lba / ($hpc * $spt);
+    my $temp = $lba % ($hpc * $spt);
+    my $head = $temp / $spt;
+    my $sect = $temp % $spt + 1;
+
+    # Source:
+    # http://www.cgsecurity.org/wiki/Intel_Partition_Table
+    if ($cyl <= 1023) {
+        return ($cyl, $head, $sect);
+    } else {
+        return (1023, 254, 63);        ## or should this be (1023, $hpc, $spt)?
+    }
+}
+
+# read_mbr($file)
+#
+# Tries to read an MBR from $file.  Returns the 512-byte MBR if
+# successful, otherwise numeric 0.
+sub read_mbr {
+    my ($file) = @_;
+    my ($retval) = 0;
+    open (FILE, '<', $file) or die "$file: open: $!\n";
+    if (-s FILE == 0) {
+       die "$file: file has zero size\n";
+    } elsif (-s FILE >= 512) {
+       my ($mbr);
+       sysread (FILE, $mbr, 512) == 512 or die "$file: read: $!\n";
+       $retval = $mbr if unpack ("v", substr ($mbr, 510)) == 0xaa55;
+    }
+    close (FILE);
+    return $retval;
+}
+
+# interpret_partition_table($mbr, $disk)
+#
+# Parses the partition-table in the specified 512-byte $mbr and
+# returns the partitions.  $disk is used for error messages.
+sub interpret_partition_table {
+    my ($mbr, $disk) = @_;
+    my (%parts);
+    for my $i (0...3) {
+       my ($bootable, $valid, $type, $lba_start, $lba_length)
+         = unpack ("C X V C x3 V V", substr ($mbr, 446 + 16 * $i, 16));
+       next if !$valid;
+
+       (print STDERR "warning: invalid partition entry $i in $disk\n"),
+         next if $bootable != 0 && $bootable != 0x80;
+
+       my ($role) = $type2role{$type};
+       (printf STDERR "warning: non-Pintos partition type 0x%02x in %s\n",
+        $type, $disk),
+         next if !defined $role;
+
+       (print STDERR "warning: duplicate \L$role\E partition in $disk\n"),
+         next if exists $parts{$role};
+
+       $parts{$role} = {START => $lba_start,
+                        SECTORS => $lba_length};
+    }
+    return %parts;
+}
+
+# find_file($base_name)
+#
+# Looks for a file named $base_name in a couple of likely spots.  If
+# found, returns the name; otherwise, returns undef.
+sub find_file {
+    my ($base_name) = @_;
+    -e && return $_ foreach $base_name, "build/$base_name";
+    return undef;
+}
+
+# read_partition_table($file)
+#
+# Reads a partition table from $file and returns the parsed
+# partitions.  Dies if partitions can't be read.
+sub read_partition_table {
+    my ($file) = @_;
+    my ($mbr) = read_mbr ($file);
+    die "$file: not a partitioned disk\n" if !$mbr;
+    return interpret_partition_table ($mbr, $file);
+}
+
+# max(@args)
+#
+# Returns the numerically largest value in @args.
+sub max {
+    my ($max) = $_[0];
+    foreach (@_[1..$#_]) {
+       $max = $_ if $_ > $max;
+    }
+    return $max;
+}
+
+1;
index c71ebc20ab4ec0b31bc29bf72e298c2ca48e1678..4b385cdf87553a07f1d5c1a2667a260b145a416c 100755 (executable)
@@ -5,6 +5,10 @@ use POSIX;
 use Fcntl;
 use File::Temp 'tempfile';
 use Getopt::Long qw(:config bundling);
+use Fcntl qw(SEEK_SET SEEK_CUR);
+
+# Read Pintos.pm from the same directory as this program.
+BEGIN { my $self = $0; $self =~ s%/+[^/]*$%%; require "$self/Pintos.pm"; }
 
 # Command-line options.
 our ($start_time) = time ();
@@ -21,16 +25,17 @@ our (@puts);                        # Files to copy into the VM.
 our (@gets);                   # Files to copy out of the VM.
 our ($as_ref);                 # Reference to last addition to @gets or @puts.
 our (@kernel_args);            # Arguments to pass to kernel.
-our (%disks) = (OS => {DEF_FN => 'os.dsk'},            # Disks to give VM.
-               FS => {DEF_FN => 'fs.dsk'},
-               SCRATCH => {DEF_FN => 'scratch.dsk'},
-               SWAP => {DEF_FN => 'swap.dsk'});
-our (@disks_by_iface) = @disks{qw (OS FS SCRATCH SWAP)};
+our (%parts);                  # Partitions.
+our ($make_disk);              # Name of disk to create.
+our ($tmp_disk) = 1;           # Delete $make_disk after run?
+our (@disks);                  # Extra disk images to pass to simulator.
+our ($loader_fn);              # Bootstrap loader.
+our (%geometry);               # IDE disk geometry.
+our ($align);                  # Partition alignment.
 
 parse_command_line ();
-find_disks ();
 prepare_scratch_disk ();
-prepare_arguments ();
+find_disks ();
 run_vm ();
 finish_scratch_disk ();
 
@@ -39,7 +44,7 @@ exit 0;
 # Parses the command line.
 sub parse_command_line {
     usage (0) if @ARGV == 0 || (@ARGV == 1 && $ARGV[0] eq '--help');
-    
+
     @kernel_args = @ARGV;
     if (grep ($_ eq '--', @kernel_args)) {
        @ARGV = ();
@@ -73,27 +78,41 @@ sub parse_command_line {
 
                    "h|help" => sub { usage (0); },
 
-                   "os-disk=s" => \$disks{OS}{FILE_NAME},
-                   "fs-disk=s" => \$disks{FS}{FILE_NAME},
-                   "scratch-disk=s" => \$disks{SCRATCH}{FILE_NAME},
-                   "swap-disk=s" => \$disks{SWAP}{FILE_NAME},
+                   "kernel=s" => \&set_part,
+                   "filesys=s" => \&set_part,
+                   "swap=s" => \&set_part,
+
+                   "filesys-size=s" => \&set_part,
+                   "scratch-size=s" => \&set_part,
+                   "swap-size=s" => \&set_part,
+
+                   "kernel-from=s" => \&set_part,
+                   "filesys-from=s" => \&set_part,
+                   "swap-from=s" => \&set_part,
 
-                   "0|disk-0|hda=s" => \$disks_by_iface[0]{FILE_NAME},
-                   "1|disk-1|hdb=s" => \$disks_by_iface[1]{FILE_NAME},
-                   "2|disk-2|hdc=s" => \$disks_by_iface[2]{FILE_NAME},
-                   "3|disk-3|hdd=s" => \$disks_by_iface[3]{FILE_NAME})
+                   "make-disk=s" => sub { $make_disk = $_[1];
+                                          $tmp_disk = 0; },
+                   "disk=s" => sub { set_disk ($_[1]); },
+                   "loader=s" => \$loader_fn,
+
+                   "geometry=s" => \&set_geometry,
+                   "align=s" => \&set_align)
          or exit 1;
     }
 
     $sim = "bochs" if !defined $sim;
     $debug = "none" if !defined $debug;
-    $vga = "window" if !defined $vga;
+    $vga = exists ($ENV{DISPLAY}) ? "window" : "none" if !defined $vga;
 
     undef $timeout, print "warning: disabling timeout with --$debug\n"
       if defined ($timeout) && $debug ne 'none';
 
     print "warning: enabling serial port for -k or --kill-on-failure\n"
       if $kill_on_failure && !$serial;
+
+    $align = "bochs",
+      print STDERR "warning: setting --align=bochs for Bochs support\n"
+       if $sim eq 'bochs' && defined ($align) && $align eq 'none';
 }
 
 # usage($exitcode).
@@ -128,15 +147,27 @@ Testing options:
                            panic, test failure, or triple fault
 Configuration options:
   -m, --mem=N              Give Pintos N MB physical RAM (default: 4)
-File system commands (for `run' command):
+File system commands:
   -p, --put-file=HOSTFN    Copy HOSTFN into VM, by default under same name
   -g, --get-file=GUESTFN   Copy GUESTFN out of VM, by default under same name
   -a, --as=FILENAME        Specifies guest (for -p) or host (for -g) file name
-Disk options: (name an existing FILE or specify SIZE in MB for a temp disk)
-  --os-disk=FILE           Set OS disk file (default: os.dsk)
-  --fs-disk=FILE|SIZE      Set FS disk file (default: fs.dsk)
-  --scratch-disk=FILE|SIZE Set scratch disk (default: scratch.dsk)
-  --swap-disk=FILE|SIZE    Set swap disk file (default: swap.dsk)
+Partition options: (where PARTITION is one of: kernel filesys scratch swap)
+  --PARTITION=FILE         Use a copy of FILE for the given PARTITION
+  --PARTITION-size=SIZE    Create an empty PARTITION of the given SIZE in MB
+  --PARTITION-from=DISK    Use of a copy of the given PARTITION in DISK
+  (There is no --kernel-size, --scratch, or --scratch-from option.)
+Disk configuration options:
+  --make-disk=DISK         Name the new DISK and don't delete it after the run
+  --disk=DISK              Also use existing DISK (may be used multiple times)
+Advanced disk configuration options:
+  --loader=FILE            Use FILE as bootstrap loader (default: loader.bin)
+  --geometry=H,S           Use H head, S sector geometry (default: 16,63)
+  --geometry=zip           Use 64 head, 32 sector geometry for USB-ZIP boot
+                           (see http://syslinux.zytor.com/usbkey.php)
+  --align=bochs            Pad out disk to cylinder to support Bochs (default)
+  --align=full             Align partition boundaries to cylinder boundary to
+                           let fdisk guess correct geometry and quiet warnings
+  --align=none             Don't align partitions at all, to save space
 Other options:
   -h, --help               Display this help message.
 EOF
@@ -201,91 +232,170 @@ sub set_as {
       if defined $as_ref->[1];
     $as_ref->[1] = $as;
 }
+
+# Sets $disk as a disk to be included in the VM to run.
+sub set_disk {
+    my ($disk) = @_;
+
+    push (@disks, $disk);
+
+    my (%pt) = read_partition_table ($disk);
+    for my $role (keys %pt) {
+       die "can't have two sources for \L$role\E partition"
+         if exists $parts{$role};
+       $parts{$role}{DISK} = $disk;
+       $parts{$role}{START} = $pt{$role}{START};
+       $parts{$role}{SECTORS} = $pt{$role}{SECTORS};
+    }
+}
 \f
 # Locates the files used to back each of the virtual disks,
 # and creates temporary disks.
 sub find_disks {
-    for my $disk (values %disks) {
-       # If there's no assigned file name but the default file exists,
-       # try to assign a default file name.
-       if (!defined ($disk->{FILE_NAME})) {
-           for my $try_fn ($disk->{DEF_FN}, "build/" . $disk->{DEF_FN}) {
-               $disk->{FILE_NAME} = $try_fn, last
-                 if -e $try_fn;
-           }
-       }
-
-       # If there's no file name, we're done.
-       next if !defined ($disk->{FILE_NAME});
-
-       if ($disk->{FILE_NAME} =~ /^\d+(\.\d+)?|\.\d+$/) {
-           # Create a temporary disk of approximately the specified
-           # size in megabytes.
-           die "OS disk can't be temporary\n" if $disk == $disks{OS};
-
-           my ($mb) = $disk->{FILE_NAME};
-           undef $disk->{FILE_NAME};
+    # Find kernel, if we don't already have one.
+    if (!exists $parts{KERNEL}) {
+       my $name = find_file ('kernel.bin');
+       die "Cannot find kernel\n" if !defined $name;
+       do_set_part ('KERNEL', 'file', $name);
+    }
 
-           my ($cyl_size) = 512 * 16 * 63;
-           extend_disk ($disk, ceil ($mb * 2) * $cyl_size);
-       } else {
-           # The file must exist and have nonzero size.
-           -e $disk->{FILE_NAME} or die "$disk->{FILE_NAME}: stat: $!\n";
-           -s _ or die "$disk->{FILE_NAME}: disk has zero size\n";
-       }
+    # Try to find file system and swap disks, if we don't already have
+    # partitions.
+    if (!exists $parts{FILESYS}) {
+       my $name = find_file ('filesys.dsk');
+       set_disk ($name) if defined $name;
+    }
+    if (!exists $parts{SWAP}) {
+       my $name = find_file ('swap.dsk');
+       set_disk ($name) if defined $name;
     }
 
-    # Warn about (potentially) missing disks.
-    die "Cannot find OS disk\n" if !defined $disks{OS}{FILE_NAME};
+    # Warn about (potentially) missing partitions.
     if (my ($project) = `pwd` =~ /\b(threads|userprog|vm|filesys)\b/) {
        if ((grep ($project eq $_, qw (userprog vm filesys)))
-           && !defined ($disks{FS}{FILE_NAME})) {
+           && !defined $parts{FILESYS}) {
            print STDERR "warning: it looks like you're running the $project ";
-           print STDERR "project, but no file system disk is present\n";
+           print STDERR "project, but no file system partition is present\n";
        }
-       if ($project eq 'vm' && !defined $disks{SWAP}{FILE_NAME}) {
+       if ($project eq 'vm' && !defined $parts{SWAP}) {
            print STDERR "warning: it looks like you're running the $project ";
-           print STDERR "project, but no swap disk is present\n";
+           print STDERR "project, but no swap partition is present\n";
        }
     }
+
+    # Open disk handle.
+    my ($handle);
+    if (!defined $make_disk) {
+       ($handle, $make_disk) = tempfile (UNLINK => $tmp_disk,
+                                         SUFFIX => '.dsk');
+    } else {
+       die "$make_disk: already exists\n" if -e $make_disk;
+       open ($handle, '>', $make_disk) or die "$make_disk: create: $!\n";
+    }
+
+    # Prepare the arguments to pass to the Pintos kernel.
+    my (@args);
+    push (@args, shift (@kernel_args))
+      while @kernel_args && $kernel_args[0] =~ /^-/;
+    push (@args, 'extract') if @puts;
+    push (@args, @kernel_args);
+    push (@args, 'append', $_->[0]) foreach @gets;
+
+    # Make disk.
+    my (%disk);
+    our (@role_order);
+    for my $role (@role_order) {
+       my $p = $parts{$role};
+       next if !defined $p;
+       next if exists $p->{DISK};
+       $disk{$role} = $p;
+    }
+    $disk{DISK} = $make_disk;
+    $disk{HANDLE} = $handle;
+    $disk{ALIGN} = $align;
+    $disk{GEOMETRY} = %geometry;
+    $disk{FORMAT} = 'partitioned';
+    $disk{LOADER} = read_loader ($loader_fn);
+    $disk{ARGS} = \@args;
+    assemble_disk (%disk);
+
+    # Put the disk at the front of the list of disks.
+    unshift (@disks, $make_disk);
+    die "can't use more than " . scalar (@disks) . "disks\n" if @disks > 4;
 }
 \f
 # Prepare the scratch disk for gets and puts.
 sub prepare_scratch_disk {
-    if (@puts) {
-       # Write ustar header and data for each file.
-       put_scratch_file ($_->[0],
-                         defined $_->[1] ? $_->[1] : $_->[0])
-         foreach @puts;
-
-       # Write end-of-archive marker.
-       write_fully ($disks{SCRATCH}{HANDLE}, $disks{SCRATCH}{FILE_NAME},
-                    "\0" x 1024);
+    return if !@gets && !@puts;
+
+    my ($p) = $parts{SCRATCH};
+    # Create temporary partition and write the files to put to it,
+    # then write an end-of-archive marker.
+    my ($part_handle, $part_fn) = tempfile (UNLINK => 1, SUFFIX => '.part');
+    put_scratch_file ($_->[0], defined $_->[1] ? $_->[1] : $_->[0],
+                     $part_handle, $part_fn)
+      foreach @puts;
+    write_fully ($part_handle, $part_fn, "\0" x 1024);
+
+    # Make sure the scratch disk is big enough to get big files
+    # and at least as big as any requested size.
+    my ($size) = round_up (max (@gets * 1024 * 1024, $p->{BYTES} || 0), 512);
+    extend_file ($part_handle, $part_fn, $size);
+    close ($part_handle);
+
+    if (exists $p->{DISK}) {
+       # Copy the scratch partition to the disk.
+       die "$p->{DISK}: scratch partition too small\n"
+         if $p->{SECTORS} * 512 < $size;
+
+       my ($disk_handle);
+       open ($part_handle, '<', $part_fn) or die "$part_fn: open: $!\n";
+       open ($disk_handle, '+<', $p->{DISK}) or die "$p->{DISK}: open: $!\n";
+       my ($start) = $p->{START} * 512;
+       sysseek ($disk_handle, $start, SEEK_SET) == $start
+         or die "$p->{DISK}: seek: $!\n";
+       copy_file ($part_handle, $part_fn, $disk_handle, $p->{DISK}, $size);
+       close ($disk_handle) or die "$p->{DISK}: close: $!\n";
+       close ($part_handle) or die "$part_fn: close: $!\n";
+    } else {
+       # Set $part_fn as the source for the scratch partition.
+       do_set_part ('SCRATCH', 'file', $part_fn);
     }
-
-    # Make sure the scratch disk is big enough to get big files.
-    extend_disk ($disks{SCRATCH}, @gets * 1024 * 1024) if @gets;
 }
 
 # Read "get" files from the scratch disk.
 sub finish_scratch_disk {
-    # We need to start reading the scratch disk from the beginning again.
-    if (@gets) {
-       close ($disks{SCRATCH}{HANDLE});
-       undef ($disks{SCRATCH}{HANDLE});
-    }
+    return if !@gets;
+
+    # Open scratch partition.
+    my ($p) = $parts{SCRATCH};
+    my ($part_handle);
+    my ($part_fn) = $p->{DISK};
+    open ($part_handle, '<', $part_fn) or die "$part_fn: open: $!\n";
+    sysseek ($part_handle, $p->{START} * 512, SEEK_SET) == $p->{START} * 512
+      or die "$part_fn: seek: $!\n";
 
     # Read each file.
-    # If reading fails, delete that file and all subsequent files.
+    # If reading fails, delete that file and all subsequent files, but
+    # don't die with an error, because that's a guest error not a host
+    # error.  (If we do exit with an error code, it fouls up the
+    # grading process.)  Instead, just make sure that the host file(s)
+    # we were supposed to retrieve is unlinked.
     my ($ok) = 1;
+    my ($part_end) = ($p->{START} + $p->{SECTORS}) * 512;
     foreach my $get (@gets) {
        my ($name) = defined ($get->[1]) ? $get->[1] : $get->[0];
-       my ($error) = get_scratch_file ($name);
-       if ($error) {
-           print STDERR "getting $name failed ($error)\n";
-           die "$name: unlink: $!\n" if !unlink ($name) && !$!{ENOENT};
-           $ok = 0;
+       if ($ok) {
+           my ($error) = get_scratch_file ($name, $part_handle, $part_fn);
+           if (!$error && sysseek ($part_handle, 0, SEEK_CUR) > $part_end) {
+               $error = "$part_fn: scratch data overflows partition";
+           }
+           if ($error) {
+               print STDERR "getting $name failed ($error)\n";
+               $ok = 0;
+           }
        }
+       die "$name: unlink: $!\n" if !$ok && !unlink ($name) && !$!{ENOENT};
     }
 }
 
@@ -313,13 +423,13 @@ sub calc_ustar_chksum {
     return unpack ("%32a*", $s);
 }
 
-# put_scratch_file($src_file_name, $dst_file_name).
+# put_scratch_file($src_file_name, $dst_file_name,
+#                  $disk_handle, $disk_file_name).
 #
-# Copies $src_file_name into the scratch disk for extraction as
-# $dst_file_name.
+# Copies $src_file_name into $disk_handle for extraction as
+# $dst_file_name.  $disk_file_name is used for error messages.
 sub put_scratch_file {
-    my ($src_file_name, $dst_file_name) = @_;
-    my ($disk_handle, $disk_file_name) = open_disk ($disks{SCRATCH});
+    my ($src_file_name, $dst_file_name, $disk_handle, $disk_file_name) = @_;
 
     print "Copying $src_file_name to scratch partition...\n";
 
@@ -367,13 +477,13 @@ sub put_scratch_file {
       if $size % 512;
 }
 
-# get_scratch_file($file).
+# get_scratch_file($get_file_name, $disk_handle, $disk_file_name)
 #
-# Copies from the scratch disk to $file.
+# Copies from $disk_handle to $get_file_name (which is created).
+# $disk_file_name is used for error messages.
 # Returns 1 if successful, 0 on failure.
 sub get_scratch_file {
-    my ($get_file_name) = @_;
-    my ($disk_handle, $disk_file_name) = open_disk ($disks{SCRATCH});
+    my ($get_file_name, $disk_handle, $disk_file_name) = @_;
 
     print "Copying $get_file_name out of $disk_file_name...\n";
 
@@ -414,35 +524,6 @@ sub get_scratch_file {
     return 0;
 }
 \f
-# Prepares the arguments to pass to the Pintos kernel,
-# and then write them into Pintos bootloader.
-sub prepare_arguments {
-    my (@args);
-    push (@args, shift (@kernel_args))
-      while @kernel_args && $kernel_args[0] =~ /^-/;
-    push (@args, 'extract') if @puts;
-    push (@args, @kernel_args);
-    push (@args, 'append', $_->[0]) foreach @gets;
-    write_cmd_line ($disks{OS}, @args);
-}
-
-# Writes @args into the Pintos bootloader at the beginning of $disk.
-sub write_cmd_line {
-    my ($disk, @args) = @_;
-
-    # Figure out command line to write.
-    my ($arg_cnt) = pack ("V", scalar (@args));
-    my ($args) = join ('', map ("$_\0", @args));
-    die "command line exceeds 128 bytes" if length ($args) > 128;
-    $args .= "\0" x (128 - length ($args));
-
-    # Write command line.
-    my ($handle, $file_name) = open_disk_copy ($disk);
-    print "Writing command line to $file_name...\n";
-    sysseek ($handle, 0x17a, 0) == 0x17a or die "$file_name: seek: $!\n";
-    syswrite ($handle, "$arg_cnt$args") or die "$file_name: write: $!\n";
-}
-\f
 # Running simulators.
 
 # Runs the selected simulator.
@@ -483,20 +564,14 @@ panic: action=fatal
 user_shortcut: keys=ctrlaltdel
 EOF
     print BOCHSRC "gdbstub: enabled=1\n" if $debug eq 'gdb';
-    if ($realtime) {
-       print BOCHSRC "clock: sync=realtime\n";
-    } else {
-       print BOCHSRC "clock: sync=none, time0=0\n";
-    }
-    print_bochs_disk_line ("ata0-master", 0);
-    print_bochs_disk_line ("ata0-slave", 1);
-    if (defined ($disks_by_iface[2]{FILE_NAME})
-       || defined ($disks_by_iface[3]{FILE_NAME})) {
-       print BOCHSRC "ata1: enabled=1, ioaddr1=0x170, ",
-         "ioaddr2=0x370, irq=15\n";
-       print_bochs_disk_line ("ata1-master", 2);
-       print_bochs_disk_line ("ata1-slave", 3);
-    }
+    print BOCHSRC "clock: sync=", $realtime ? 'realtime' : 'none',
+      ", time0=0\n";
+    print BOCHSRC "ata1: enabled=1, ioaddr1=0x170, ioaddr2=0x370, irq=15\n"
+      if @disks > 2;
+    print_bochs_disk_line ("ata0-master", $disks[0]);
+    print_bochs_disk_line ("ata0-slave", $disks[1]);
+    print_bochs_disk_line ("ata1-master", $disks[2]);
+    print_bochs_disk_line ("ata1-slave", $disks[3]);
     if ($vga ne 'terminal') {
        if ($serial) {
            my $mode = defined ($squish_pty) ? "term" : "file";
@@ -527,17 +602,11 @@ EOF
     }
 }
 
-# print_bochs_disk_line($device, $iface)
-#
-# If IDE interface $iface has a disk attached, prints a bochsrc.txt
-# line for attaching it to $device.
 sub print_bochs_disk_line {
-    my ($device, $iface) = @_;
-    my ($disk) = $disks_by_iface[$iface];
-    my ($file) = $disk->{FILE_NAME};
-    if (defined $file) {
+    my ($device, $disk) = @_;
+    if (defined $disk) {
        my (%geom) = disk_geometry ($disk);
-       print BOCHSRC "$device: type=disk, path=$file, mode=flat, ";
+       print BOCHSRC "$device: type=disk, path=$disk, mode=flat, ";
        print BOCHSRC "cylinders=$geom{C}, heads=$geom{H}, spt=$geom{S}, ";
        print BOCHSRC "translation=none\n";
     }
@@ -550,11 +619,11 @@ sub run_qemu {
     print "warning: qemu doesn't support jitter\n"
       if defined $jitter;
     my (@cmd) = ('qemu');
-    for my $iface (0...3) {
-       my ($option) = ('-hda', '-hdb', '-hdc', '-hdd')[$iface];
-       push (@cmd, $option, $disks_by_iface[$iface]{FILE_NAME})
-         if defined $disks_by_iface[$iface]{FILE_NAME};
-    }
+    push (@cmd, '-no-kqemu');
+    push (@cmd, '-hda', $disks[0]) if defined $disks[0];
+    push (@cmd, '-hdb', $disks[1]) if defined $disks[1];
+    push (@cmd, '-hdc', $disks[2]) if defined $disks[2];
+    push (@cmd, '-hdd', $disks[3]) if defined $disks[3];
     push (@cmd, '-m', $mem);
     push (@cmd, '-net', 'none');
     push (@cmd, '-nographic') if $vga eq 'none';
@@ -583,8 +652,7 @@ sub run_player {
     player_unsup ("--kill-on-failure"), undef $kill_on_failure
       if defined $kill_on_failure;
 
-    # Memory size must be multiple of 4.
-    $mem = int (($mem + 3) / 4) * 4;
+    $mem = round_up ($mem, 4); # Memory must be multiple of 4 MB.
 
     open (VMX, ">", "pintos.vmx") or die "pintos.vmx: create: $!\n";
     chmod 0777 & ~umask, "pintos.vmx";
@@ -601,8 +669,6 @@ gui.exitOnCLIHLT = TRUE
 gui.powerOnAtStartUp = TRUE
 EOF
 
-
-
     print VMX <<EOF if $serial;
 serial0.present = TRUE
 serial0.fileType = "pipe"
@@ -612,9 +678,8 @@ serial0.tryNoRxLoss = "TRUE"
 EOF
 
     for (my ($i) = 0; $i < 4; $i++) {
-       my ($disk) = $disks_by_iface[$i];
-       my ($dsk) = $disk->{FILE_NAME};
-       next if !defined $dsk;
+       my ($dsk) = $disks[$i];
+       last if !defined $dsk;
 
        my ($device) = "ide" . int ($i / 2) . ":" . ($i % 2);
        my ($pln) = "$device.pln";
@@ -631,7 +696,7 @@ EOF
        close (URANDOM);
        my ($cid) = unpack ("L", $bytes);
 
-       my (%geom) = disk_geometry ($disk);
+       my (%geom) = disk_geometry ($dsk);
        open (PLN, ">", $pln) or die "$pln: create: $!\n";
        print PLN <<EOF;
 version=1
@@ -671,45 +736,8 @@ EOF
 \f
 # Disk utilities.
 
-# open_disk($disk)
-#
-# Opens $disk, if it is not already open, and returns its file handle
-# and file name.
-sub open_disk {
-    my ($disk) = @_;
-    if (!defined ($disk->{HANDLE})) {
-       if ($disk->{FILE_NAME}) {
-           sysopen ($disk->{HANDLE}, $disk->{FILE_NAME}, O_RDWR)
-             or die "$disk->{FILE_NAME}: open: $!\n";
-       } else {
-           ($disk->{HANDLE}, $disk->{FILE_NAME}) = tempfile (UNLINK => 1,
-                                                            SUFFIX => '.dsk');
-       }
-    }
-    return ($disk->{HANDLE}, $disk->{FILE_NAME});
-}
-
-# open_disk_copy($disk)
-#
-# Makes a temporary copy of $disk and returns its file handle and file name.
-sub open_disk_copy {
-    my ($disk) = @_;
-    die if !$disk->{FILE_NAME};
-
-    my ($orig_handle, $orig_file_name) = open_disk ($disk);
-    my ($cp_handle, $cp_file_name) = tempfile (UNLINK => 1, SUFFIX => '.dsk');
-    copy_file ($orig_handle, $orig_file_name, $cp_handle, $cp_file_name,
-              -s $orig_handle);
-    return ($disk->{HANDLE}, $disk->{FILE_NAME}) = ($cp_handle, $cp_file_name);
-}
-
-# extend_disk($disk, $size)
-#
-# Extends $disk, if necessary, so that it is at least $size bytes
-# long.
-sub extend_disk {
-    my ($disk, $size) = @_;
-    my ($handle, $file_name) = open_disk ($disk);
+sub extend_file {
+    my ($handle, $file_name, $size) = @_;
     if (-s ($handle) < $size) {
        sysseek ($handle, $size - 1, 0) == $size - 1
          or die "$file_name: seek: $!\n";
@@ -723,61 +751,18 @@ sub extend_disk {
 # Examines $file and returns a valid IDE disk geometry for it, as a
 # hash.
 sub disk_geometry {
-    my ($disk) = @_;
-    my ($file) = $disk->{FILE_NAME};
+    my ($file) = @_;
     my ($size) = -s $file;
     die "$file: stat: $!\n" if !defined $size;
-    die "$file: size not a multiple of 512 bytes\n" if $size % 512;
+    die "$file: size $size not a multiple of 512 bytes\n" if $size % 512;
     my ($cyl_size) = 512 * 16 * 63;
     my ($cylinders) = ceil ($size / $cyl_size);
-    extend_disk ($disk, $cylinders * $cyl_size) if $size % $cyl_size;
 
     return (CAPACITY => $size / 512,
            C => $cylinders,
            H => 16,
            S => 63);
 }
-
-# copy_file($from_handle, $from_file_name, $to_handle, $to_file_name, $size)
-#
-# Copies $size bytes from $from_handle to $to_handle.
-# $from_file_name and $to_file_name are used in error messages.
-sub copy_file {
-    my ($from_handle, $from_file_name, $to_handle, $to_file_name, $size) = @_;
-
-    while ($size > 0) {
-       my ($chunk_size) = 4096;
-       $chunk_size = $size if $chunk_size > $size;
-       $size -= $chunk_size;
-
-       my ($data) = read_fully ($from_handle, $from_file_name, $chunk_size);
-       write_fully ($to_handle, $to_file_name, $data);
-    }
-}
-
-# read_fully($handle, $file_name, $bytes)
-#
-# Reads exactly $bytes bytes from $handle and returns the data read.
-# $file_name is used in error messages.
-sub read_fully {
-    my ($handle, $file_name, $bytes) = @_;
-    my ($data);
-    my ($read_bytes) = sysread ($handle, $data, $bytes);
-    die "$file_name: read: $!\n" if !defined $read_bytes;
-    die "$file_name: unexpected end of file\n" if $read_bytes != $bytes;
-    return $data;
-}
-
-# write_fully($handle, $file_name, $data)
-#
-# Write $data to $handle.
-# $file_name is used in error messages.
-sub write_fully {
-    my ($handle, $file_name, $data) = @_;
-    my ($written_bytes) = syswrite ($handle, $data);
-    die "$file_name: write: $!\n" if !defined $written_bytes;
-    die "$file_name: short write\n" if $written_bytes != length $data;
-}
 \f
 # Subprocess utilities.
 
index 662b2e57d7eebccdac6ab1173702a8148a25af2a..87b1563e69c517070e81ad32c36adfabdf6df2a5 100755 (executable)
 use strict;
 use warnings;
 use POSIX;
-use Getopt::Long;
+use Getopt::Long qw(:config bundling);
 use Fcntl 'SEEK_SET';
 
-GetOptions ("h|help" => sub { usage (0); })
+# Read Pintos.pm from the same directory as this program.
+BEGIN { my $self = $0; $self =~ s%/+[^/]*$%%; require "$self/Pintos.pm"; }
+
+our ($disk_fn);                        # Output disk file name.
+our (%parts);                  # Partitions.
+our ($format);                 # "partitioned" (default) or "raw"
+our (%geometry);               # IDE disk geometry.
+our ($align);                  # Align partitions on cylinders?
+our ($loader_fn);              # File name of loader.
+our ($include_loader);         # Include loader?
+our (@kernel_args);            # Kernel arguments.
+
+if (grep ($_ eq '--', @ARGV)) {
+    @kernel_args = @ARGV;
+    @ARGV = ();
+    while ((my $arg = shift (@kernel_args)) ne '--') {
+       push (@ARGV, $arg);
+    }
+}
+
+GetOptions ("h|help" => sub { usage (0); },
+
+           "kernel=s" => \&set_part,
+           "filesys=s" => \&set_part,
+           "scratch=s" => \&set_part,
+           "swap=s" => \&set_part,
+
+           "filesys-size=s" => \&set_part,
+           "scratch-size=s" => \&set_part,
+           "swap-size=s" => \&set_part,
+
+           "kernel-from=s" => \&set_part,
+           "filesys-from=s" => \&set_part,
+           "scratch-from=s" => \&set_part,
+           "swap-from=s" => \&set_part,
+
+           "format=s" => \$format,
+           "loader:s" => \&set_loader,
+           "no-loader" => \&set_no_loader,
+           "geometry=s" => \&set_geometry,
+           "align=s" => \&set_align)
   or exit 1;
-usage (1) if @ARGV != 2;
+usage (1) if @ARGV != 1;
+
+$disk_fn = $ARGV[0];
+die "$disk_fn: already exists\n" if -e $disk_fn;
+
+# Sets the loader to copy to the MBR.
+sub set_loader {
+    die "can't specify both --loader and --no-loader\n"
+      if defined ($include_loader) && !$include_loader;
+    $include_loader = 1;
+    $loader_fn = $_[1] if $_[1] ne '';
+}
+
+# Disables copying a loader to the MBR.
+sub set_no_loader {
+    die "can't specify both --loader and --no-loader\n"
+      if defined ($include_loader) && $include_loader;
+    $include_loader = 0;
+}
+
+# Figure out whether to include a loader.
+$include_loader = exists ($parts{KERNEL}) && $format eq 'partitioned'
+  if !defined ($include_loader);
+die "can't write loader to raw disk\n" if $include_loader && $format eq 'raw';
+die "can't write command-line arguments without --loader or --kernel\n"
+  if @kernel_args && !$include_loader;
+print STDERR "warning: --loader only makes sense without --kernel "
+  . "if this disk will be used to load a kernel from another disk\n"
+  if $include_loader && !exists ($parts{KERNEL});
+
+# Open disk.
+my ($disk_handle);
+open ($disk_handle, '>', $disk_fn) or die "$disk_fn: create: $!\n";
 
-my ($disk, $mb) = @ARGV;
-die "$disk: already exists\n" if -e $disk;
-die "\"$mb\" is not a valid size in megabytes\n"
-  if $mb <= 0 || $mb > 1024 || $mb !~ /^\d+(\.\d+)?|\.\d+/;
+# Read loader.
+my ($loader);
+$loader = read_loader ($loader_fn) if $include_loader;
 
-my ($cyl_cnt) = ceil ($mb * 2);
-my ($cyl_bytes) = 512 * 16 * 63;
-my ($bytes) = $cyl_bytes * $cyl_cnt;
+# Write disk.
+my (%disk) = %parts;
+$disk{DISK} = $disk_fn;
+$disk{HANDLE} = $disk_handle;
+$disk{ALIGN} = $align;
+$disk{GEOMETRY} = %geometry;
+$disk{FORMAT} = $format;
+$disk{LOADER} = $loader;
+$disk{ARGS} = \@kernel_args;
+assemble_disk (%disk);
 
-open (DISK, '>', $disk) or die "$disk: create: $!\n";
-sysseek (DISK, $bytes - 1, SEEK_SET) or die "$disk: seek: $!\n";
-syswrite (DISK, "\0", 1) == 1 or die "$disk: write: $!\n";
-close (DISK) or die "$disk: close: $!\n";
+# Done.
+exit 0;
 
 sub usage {
     print <<'EOF';
 pintos-mkdisk, a utility for creating Pintos virtual disks
-Usage: pintos DISKFILE MB
-where DISKFILE is the file to use for the disk
-  and MB is the disk size in (approximate) megabytes.
-Options:
-  -h, --help        Display this help message.
+Usage: pintos-mkdisk [OPTIONS] DISK [-- ARGUMENT...]
+where DISK is the virtual disk to create,
+      each ARGUMENT is inserted into the command line written to DISK,
+  and each OPTION is one of the following options.
+Partition options: (where PARTITION is one of: kernel filesys scratch swap)
+  --PARTITION=FILE         Use a copy of FILE for the given PARTITION
+  --PARTITION-size=SIZE    Create an empty PARTITION of the given SIZE in MB
+  --PARTITION-from=DISK    Use of a copy of the given PARTITION in DISK
+  (There is no --kernel-size option.)
+Output disk options:
+  --format=partitioned     Write partition table to output (default)
+  --format=raw             Do not write partition table to output
+  (Pintos can only use partitioned disks.)
+Partitioned format output options:
+  --loader[=FILE]          Get bootstrap loader from FILE (default: loader.bin
+                           if --kernel option is specified, empty otherwise)
+  --no-loader              Do not include a bootstrap loader
+  --geometry=H,S           Use H head, S sector geometry (default: 16, 63)
+  --geometry=zip           Use 64 head, 32 sector geometry for USB-ZIP boot
+                           per http://syslinux.zytor.com/usbkey.php
+  --align=bochs            Round size to cylinder for Bochs support (default)
+  --align=full             Align partition boundaries to cylinder boundary to
+                           let fdisk guess correct geometry and quiet warnings
+  --align=none             Don't align partitions at all, to save space
+Other options:
+  -h, --help               Display this help message.
 EOF
-    exit (@_);
+    exit ($_[0]);
 }
diff --git a/src/utils/pintos-set-cmdline b/src/utils/pintos-set-cmdline
new file mode 100644 (file)
index 0000000..8c8f702
--- /dev/null
@@ -0,0 +1,42 @@
+#! /usr/bin/perl -w
+
+use strict;
+use Fcntl 'SEEK_SET';
+
+# Read Pintos.pm from the same directory as this program.
+BEGIN { my $self = $0; $self =~ s%/+[^/]*$%%; require "$self/Pintos.pm"; }
+
+# Get command-line arguments.
+usage (0) if @ARGV == 1 && $ARGV[0] eq '--help';
+usage (1) if @ARGV < 2 || $ARGV[1] ne '--';
+my ($disk, undef, @kernel_args) = @ARGV;
+
+# Open disk.
+my ($handle);
+open ($handle, '+<', $disk) or die "$disk: open: $!\n";
+
+# Check that it's a partitioned disk with a Pintos loader.
+my ($buffer) = read_fully ($handle, $disk, 512);
+unpack ("x510 v", $buffer) == 0xaa55 or die "$disk: not a partitioned disk\n";
+$buffer =~ /Pintos/ or die "$disk: does not contain Pintos loader\n";
+
+# Write the command line.
+our ($LOADER_SIZE);
+sysseek ($handle, $LOADER_SIZE, SEEK_SET) == $LOADER_SIZE
+  or die "$disk: seek: $!\n";
+write_fully ($handle, $disk, make_kernel_command_line (@kernel_args));
+
+# Close disk.
+close ($handle) or die "$disk: close: $!\n";
+
+exit 0;
+
+sub usage {
+    print <<'EOF';
+pintos-set-cmdline, a utility for changing the command line in Pintos disks
+Usage: pintos-set-cmdline DISK -- [ARGUMENT...]
+where DISK is a bootable disk containing a Pintos loader
+  and each ARGUMENT is inserted into the command line written to DISK.
+EOF
+    exit ($_[0]);
+}
index a5ea6b02f8181e3b7d8cd3a05d27ec61a1716d8a..e3c33a755b4ffcfc5236ddea4cb6524630712bae 100644 (file)
@@ -1,6 +1,6 @@
 # -*- makefile -*-
 
-os.dsk: DEFINES = -DUSERPROG -DFILESYS -DVM
+kernel.bin: DEFINES = -DUSERPROG -DFILESYS -DVM
 KERNEL_SUBDIRS = threads devices lib lib/kernel userprog filesys vm
 TEST_SUBDIRS = tests/userprog tests/vm tests/filesys/base
 GRADING_FILE = $(SRCDIR)/tests/vm/Grading