From: Godmar Back Date: Fri, 22 Aug 2008 02:21:23 +0000 (+0000) Subject: Applied patch set 6 by Ben, derived from Anthony's megapatch, and minor X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8fc9be2744dbe24bf5676a0502b36d5afe3b9ebb;p=pintos-anon Applied patch set 6 by Ben, derived from Anthony's megapatch, and minor patches by me. This introduces, among others: - booting from BIOS - PCI support - USB support - RTC support - PCSpeaker support --- diff --git a/AUTHORS b/AUTHORS index d1248e5..557ed34 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,7 +1,10 @@ src -*- text -*- --- -* Most code written by Ben Pfaff . +* Pintos core originally written by Ben Pfaff . + +* Support for PCI, USB, GRUB, partitions, UHCI, and other features + contributed by Anthony Romano . * The original structure and form of this operating system is inspired by the Nachos system from the University of California, Berkeley. A @@ -9,10 +12,6 @@ src -*- text -*- Nachos C++ code into C. These files bear the original UCB license notice. -* Some of the source code is 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. - projects -------- diff --git a/LICENSE b/LICENSE index 8702541..64103d0 100644 --- a/LICENSE +++ b/LICENSE @@ -54,42 +54,3 @@ license: PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. */ - -Also, code derived from MIT's 6.828 course code is subject to the -following license: - -/* - * 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. - */ diff --git a/doc/bibliography.texi b/doc/bibliography.texi index a23c4c7..f12bca9 100644 --- a/doc/bibliography.texi +++ b/doc/bibliography.texi @@ -67,6 +67,10 @@ Datasheet for PC timer chip. @uref{specs/8259A.pdf, , Intel 8259A Programmable Interrupt Controller (8259A/8259A-2)}. Datasheet for PC interrupt controller chip. +@bibdfn{MC146818A} +@uref{specs/mc146818a.pdf, , Motorola MC146818A Real Time Clock Plus +Ram (RTC)}. Datasheet for PC real-time clock chip. + @node Software References @section Software References @@ -102,6 +106,18 @@ Edition}. 80@var{x}86-specific parts of the Unix interface. Interface---DRAFT---24 April 2001}. A draft of a revised version of @bibref{SysV-ABI} which was never completed. +@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. + +@bibdfn{SUSv3} +The Open Group, @uref{http://www.unix.org/single_unix_specification/, +, Single UNIX Specification V3}, 2001. + @node Operating System Design References @section Operating System Design References diff --git a/doc/filesys.texi b/doc/filesys.texi index 2567d6a..363b9d9 100644 --- a/doc/filesys.texi +++ b/doc/filesys.texi @@ -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. @@ -303,7 +304,7 @@ are straightforward once the above syscalls are implemented. We have also provided @command{pwd}, which is not so straightforward. The @command{shell} program implements @command{cd} internally. -The @code{pintos} @option{put} and @option{get} commands should now +The @code{pintos} @option{extract} and @option{get} commands should now accept full path names, assuming that the directories used in the paths have already been created. This should not require any significant extra effort on your part. @@ -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 diff --git a/doc/installation.texi b/doc/installation.texi index 0fc7b06..f6dfe44 100644 --- a/doc/installation.texi +++ b/doc/installation.texi @@ -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 diff --git a/doc/intro.texi b/doc/intro.texi index 6c49f74..8d3f455 100644 --- a/doc/intro.texi +++ b/doc/intro.texi @@ -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}.) @@ -499,8 +494,11 @@ FOR A PARTICULAR PURPOSE. @node Acknowledgements @section Acknowledgements -Pintos and this documentation were written by Ben Pfaff -@email{blp@@cs.stanford.edu}. +The Pintos core and this documentation were originally written by Ben +Pfaff @email{blp@@cs.stanford.edu}. + +Support for PCI, USB, GRUB, partitions, UHCI, and other features +were contributed by Anthony Romano @email{chz@@vt.edu}. The GDB macros supplied with Pintos were written by Godmar Back @email{gback@@cs.vt.edu}, and their documentation is adapted from his @@ -510,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 diff --git a/doc/license.texi b/doc/license.texi index 571a131..412b5a7 100644 --- a/doc/license.texi +++ b/doc/license.texi @@ -60,42 +60,3 @@ BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. @end quotation - -Code derived from MIT's 6.828 course code is subject to the -following license: - -@quotation -Copyright @copyright{} 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. -@end quotation diff --git a/doc/reference.texi b/doc/reference.texi index bcdcd63..cd2719f 100644 --- a/doc/reference.texi +++ b/doc/reference.texi @@ -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 @@ -124,17 +125,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{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 +177,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 +191,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 @@ -539,7 +565,7 @@ synchronization primitives to help out. * Semaphores:: * Locks:: * Monitors:: -* Optimization Barriers:: +* Optimization Barriers:: @end menu @node Disabling Interrupts @@ -1219,7 +1245,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 diff --git a/doc/threads.texi b/doc/threads.texi index 6eec6bb..c5d6329 100644 --- a/doc/threads.texi +++ b/doc/threads.texi @@ -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 @@ -240,6 +256,22 @@ serial drivers. Interrupt queue, for managing a circular queue that both kernel threads and interrupt handlers want to access. Used by the keyboard and serial drivers. + +@item rtc.c +@itemx rtc.h +Real-time clock driver, to enable the kernel to determine the current +date and time. By default, this is only used by @file{thread/init.c} +to choose an initial seed for the random number generator. + +@item speaker.c +@itemx speaker.h +Driver that can produce tones on the PC speaker. + +@item pit.c +@itemx pit.h +Code to configure the 8254 Programmable Interrupt Timer. This code is +used by both @file{devices/timer.c} and @file{devices/speaker.c} +because each devices uses one of the PIT's output channel. @end table @node lib files @@ -277,7 +309,11 @@ more information. @item random.c @itemx random.h -Pseudo-random number generator. +Pseudo-random number generator. The actual sequence of random values +will not vary from one Pintos run to another, unless you do one of +three things: specify a new random seed value on the @option{-rs} +kernel command-line option on each run, or use a simulator other than +Bochs, or specify the @option{-r} option to @command{pintos}. @item round.h Macros for rounding. diff --git a/doc/userprog.texi b/doc/userprog.texi index 612bb81..53c3ca6 100644 --- a/doc/userprog.texi +++ b/doc/userprog.texi @@ -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'') @@ -204,21 +206,20 @@ commands for copying files out of a VM are similar, but substitute @option{-g} for @option{-p}. Incidentally, these commands work by passing special commands -@command{put} and @command{get} on the kernel's command line and copying -to and from a special simulated ``scratch'' disk. If you're very +@command{extract} and @command{get} on the kernel's command line and copying +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 @@ -834,7 +836,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} @@ -855,6 +857,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 diff --git a/doc/vm.texi b/doc/vm.texi index fdbc5c6..ab64a4d 100644 --- a/doc/vm.texi +++ b/doc/vm.texi @@ -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 diff --git a/src/Makefile.build b/src/Makefile.build index 277373e..21e2847 100644 --- a/src/Makefile.build +++ b/src/Makefile.build @@ -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,16 +21,26 @@ 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/timer.c # Timer device. +devices_SRC = devices/pit.c # Programmable interrupt timer chip. +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. +devices_SRC += devices/speaker.c # PC speaker. +devices_SRC += devices/pci.c # PCI bus. +devices_SRC += devices/usb.c # USB layer. +devices_SRC += devices/usb_uhci.c # UHCI device. +devices_SRC += devices/usb_ehci.c # EHCI device. +devices_SRC += devices/usb_storage.c # USB mass storage class driver. +devices_SRC += devices/usb_hub.c # USB hub class driver. # Library code shared between kernel and user programs. lib_SRC = lib/debug.c # Debug helpers. @@ -37,7 +48,8 @@ lib_SRC += lib/random.c # Pseudo-random numbers. lib_SRC += lib/stdio.c # I/O library. lib_SRC += lib/stdlib.c # Utility functions. lib_SRC += lib/string.c # String functions. -lib_SRC += lib/arithmetic.c +lib_SRC += lib/arithmetic.c # 64-bit arithmetic for GCC. +lib_SRC += lib/ustar.c # Unix standard tar format utilities. # Kernel-specific library code. lib/kernel_SRC = lib/kernel/debug.c # Debug helpers. @@ -76,24 +88,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/Makefile.userprog b/src/Makefile.userprog index f90b08c..0df391a 100644 --- a/src/Makefile.userprog +++ b/src/Makefile.userprog @@ -12,7 +12,8 @@ lib_SRC += lib/random.c # Pseudo-random numbers. lib_SRC += lib/stdio.c # I/O library. lib_SRC += lib/stdlib.c # Utility functions. lib_SRC += lib/string.c # String functions. -lib_SRC += lib/arithmetic.c +lib_SRC += lib/arithmetic.c # 64-bit arithmetic for GCC. +lib_SRC += lib/ustar.c # Unix standard tar format utilities. # User level only library code. lib/user_SRC = lib/user/debug.c # Debug helpers. diff --git a/src/devices/block.c b/src/devices/block.c new file mode 100644 index 0000000..452c433 --- /dev/null +++ b/src/devices/block.c @@ -0,0 +1,223 @@ +#include "devices/block.h" +#include +#include +#include +#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; +} + +/* 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 index 0000000..21732d6 --- /dev/null +++ b/src/devices/block.h @@ -0,0 +1,74 @@ +#ifndef DEVICES_BLOCK_H +#define DEVICES_BLOCK_H + +#include +#include + +/* 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 + +/* 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); + +/* 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 index 14fc631..0000000 --- a/src/devices/disk.c +++ /dev/null @@ -1,571 +0,0 @@ -#include "devices/disk.h" -#include -#include -#include -#include -#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); -} - -/* 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]); -} - -/* 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); -} - -/* 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); -} - -/* 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 index 3bcbb9a..0000000 --- a/src/devices/disk.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef DEVICES_DISK_H -#define DEVICES_DISK_H - -#include -#include - -/* 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 index 0000000..2cc0292 --- /dev/null +++ b/src/devices/ide.c @@ -0,0 +1,527 @@ +#include "devices/ide.h" +#include +#include +#include +#include +#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]); + } +} + +/* 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; +} + +/* 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 + }; + +/* 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); +} + +/* 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); +} + +/* 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 index 0000000..b35da5e --- /dev/null +++ b/src/devices/ide.h @@ -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 index 0000000..7e97332 --- /dev/null +++ b/src/devices/partition.c @@ -0,0 +1,324 @@ +#include "devices/partition.h" +#include +#include +#include +#include +#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 index 0000000..47fea4d --- /dev/null +++ b/src/devices/partition.h @@ -0,0 +1,8 @@ +#ifndef DEVICES_PARTITION_H +#define DEVICES_PARTITION_H + +struct block; + +void partition_scan (struct block *); + +#endif /* devices/partition.h */ diff --git a/src/devices/pci.c b/src/devices/pci.c new file mode 100755 index 0000000..50d5d57 --- /dev/null +++ b/src/devices/pci.c @@ -0,0 +1,811 @@ +#define PCI_TRANSLATION_ENABLE 1 + +#include "devices/pci.h" +#include "threads/malloc.h" +#include "threads/interrupt.h" +#include "threads/pte.h" +#include "threads/io.h" +#include "pci_lookup.h" +#include +#include +#include +#include +#include + +extern uint32_t *base_page_dir; + +#define PCI_REG_ADDR 0xcf8 +#define PCI_REG_DATA 0xcfc +#define pci_config_offset(bus, dev, func, reg) (0x80000000 | ((bus) << 16) | ((dev) << 11) | ((func) << 8) | (reg & (~3))) + +#define PCI_MAX_DEV_PER_BUS 32 +#define PCI_MAX_FUNC_PER_DEV 8 + +#define PCI_HEADER_SZ 256 + +#define PCI_CMD_IO 0x001 /* enable io response */ +#define PCI_CMD_MEMORY 0x002 /* enable memory response */ +#define PCI_CMD_MASTER 0x004 /* enable bus mastering */ +#define PCI_CMD_SPECIAL 0x008 /* enable special cycles */ +#define PCI_CMD_INVALIDATE 0x010 /* memory write + invalidate */ +#define PCI_CMD_PALETTE 0x020 /* palette snooping */ +#define PCI_CMD_PARITY 0x040 /* parity checking */ +#define PCI_CMD_WAIT 0x080 /* address/data stepping */ +#define PCI_CMD_SERR 0x100 /* serr */ +#define PCI_CMD_FASTBACK 0x200 /* back-to-back writing */ +#define PCI_CMD_INTX_DISABLE 0x400 /* emulation disable */ + +#define PCI_STATUS_CAPLIST 0x10 /* capability list */ +#define PCI_STATUS_66MHZ 0x20 /* 66mhz pci 2.1 bus */ +#define PCI_STATUS_UDF 0x40 /* user definable features */ +#define PCI_STATUS_FASTBACK 0x80 /* fast back to back */ +#define PCI_STATUS_PARITY 0x100 /* parity error detected */ +#define PCI_STATUS_DEVSEL 0x600 /* devsel mask */ +#define PCI_STATUS_DEVSEL_FAST 0 +#define PCI_STATUS_DEVSEL_MED 0x200 +#define PCI_STATUS_DEVSEL_SLOW 0x400 +#define PCI_STATUS_SIG_ABORT 0x0800 /* set on target abort */ +#define PCI_STATUS_REC_ABORT 0x1000 /* master ack of abort */ +#define PCI_STATUS_REC_ABORT_M 0x2000 /* set on master abort */ +#define PCI_STATUS_SERR 0x4000 /* system error */ +#define PCI_STATUS_PARITY2 0x8000 /* set on parity err */ + +#define PCI_HEADER_NORMAL 0 +#define PCI_HEADER_BRIDGE 1 +#define PCI_HEADER_CARDBUS 2 + +#define PCI_HEADER_MASK 0x7f +#define PCI_HEADER_MULTIFUNC 0x80 + +#define pci_get_reg_offset(x) (&(((pci_config_header*)(NULL))->x)) + +#define PCI_VENDOR_INVALID 0xffff + +#define PCI_BASEADDR_IO 0x00000001 +#define PCI_BASEADDR_TYPEMASK 0x00000006 +#define PCI_BASEADDR_32BIT 0x00000000 +#define PCI_BASEADDR_64BIT 0x00000004 +#define PCI_BASEADDR_PREFETCH 0x00000008 +#define PCI_BASEADDR_ADDR32 0xfffffff0 +#define PCI_BASEADDR_ADDR64 0xfffffffffffffff0 +#define PCI_BASEADDR_IOPORT 0xfffffffc + +#pragma pack(1) + +struct pci_config_header +{ + uint16_t pci_vendor_id; + uint16_t pci_device_id; + + uint16_t pci_command; + uint16_t pci_status; + + uint8_t pci_revision; + + /* pci class */ + uint8_t pci_interface; + uint8_t pci_minor; + uint8_t pci_major; + + uint8_t pci_cachelinesz; + uint8_t pci_latency; + uint8_t pci_header; /* header type */ + uint8_t pci_bist; /* self test */ + + uint32_t pci_base_reg[6]; + uint32_t pci_cis; /* cardbus cis pointer */ + uint16_t pci_sub_vendor_id; + uint16_t pci_sub_id; + uint32_t pci_rom_addr; + uint8_t pci_capabilities; + uint8_t pci_resv[7]; + uint8_t pci_int_line; + uint8_t pci_int_pin; + uint8_t pci_min_gnt; + uint8_t pci_max_latency; +}; + +#pragma pack() + +#define PCI_BASE_COUNT 6 +struct pci_dev +{ + struct pci_config_header pch; + uint8_t bus, dev, func; + int base_reg_size[PCI_BASE_COUNT]; + + pci_handler_func *irq_handler; + void *irq_handler_aux; + + struct list io_ranges; + struct list_elem peer; + struct list_elem int_peer; +}; + +enum pci_io_type +{ PCI_IO_MEM, PCI_IO_PORT }; + +/* represents a PCI IO range */ +struct pci_io +{ + struct pci_dev *dev; + enum pci_io_type type; + size_t size; /* bytes in range */ + union + { + void *ptr; /* virtual memory address */ + int port; /* io port */ + } addr; + struct list_elem peer; /* linkage */ +}; + + +static void pci_write_config (int bus, int dev, int func, int reg, + int size, uint32_t data); +static uint32_t pci_read_config (int bus, int dev, int func, int reg, + int size); +static void pci_read_all_config (int bus, int dev, int func, + struct pci_config_header *pch); +static int pci_scan_bus (int bus); +static int pci_probe (int bus, int dev, int func, + struct pci_config_header *ph); +static int pci_pci_bridge (struct pci_dev *pd); +static void pci_power_on (struct pci_dev *pd); +static void pci_setup_io (struct pci_dev *pd); +static void pci_interrupt (struct intr_frame *); +static void pci_print_dev_info (struct pci_dev *pd); +static void *pci_alloc_mem (void *phys_ptr, int pages); + +static struct list devices; +static struct list int_devices; + +/* number of pages that have been allocated to pci devices in the pci zone */ +static int num_pci_pages; + +void +pci_init (void) +{ + list_init (&devices); + list_init (&int_devices); + + num_pci_pages = 0; + + pci_scan_bus (0); + pci_print_stats (); +} + +struct pci_dev * +pci_get_device (int vendor, int device, int func, int n) +{ + struct list_elem *e; + int count; + + count = 0; + e = list_begin (&devices); + while (e != list_end (&devices)) + { + struct pci_dev *pd; + + pd = list_entry (e, struct pci_dev, peer); + if (pd->pch.pci_vendor_id == vendor && pd->pch.pci_device_id == device) + if (pd->func == func) + { + if (count == n) + return pd; + count++; + } + + e = list_next (e); + } + + return NULL; +} + +struct pci_dev * +pci_get_dev_by_class (int major, int minor, int iface, int n) +{ + struct list_elem *e; + int count; + + count = 0; + e = list_begin (&devices); + while (e != list_end (&devices)) + { + struct pci_dev *pd; + + pd = list_entry (e, struct pci_dev, peer); + if (pd->pch.pci_major == major && pd->pch.pci_minor == minor && + pd->pch.pci_interface == iface) + { + if (count == n) + return pd; + count++; + } + + e = list_next (e); + } + + return NULL; + +} + +struct pci_io * +pci_io_enum (struct pci_dev *pio, struct pci_io *last) +{ + struct list_elem *e; + + if (last != NULL) + e = list_next (&last->peer); + else + e = list_begin (&pio->io_ranges); + + if (e == list_end (&pio->io_ranges)) + return NULL; + + return list_entry (e, struct pci_io, peer); +} + +void +pci_register_irq (struct pci_dev *pd, pci_handler_func * f, void *aux) +{ + int int_vec; + ASSERT (pd != NULL); + ASSERT (pd->irq_handler == NULL); + + pd->irq_handler_aux = aux; + pd->irq_handler = f; + int_vec = pd->pch.pci_int_line + 0x20; + + list_push_back (&int_devices, &pd->int_peer); + + /* ensure that pci interrupt is hooked */ + if (!intr_is_registered (int_vec)) + intr_register_ext (int_vec, pci_interrupt, "PCI"); +} + +void +pci_unregister_irq (struct pci_dev *pd) +{ + ASSERT (pd != NULL); + + intr_disable (); + list_remove (&pd->int_peer); + intr_enable (); + + pd->irq_handler = NULL; + pd->irq_handler_aux = NULL; +} + +size_t +pci_io_size (struct pci_io *pio) +{ + ASSERT (pio != NULL); + + return pio->size; +} + +void +pci_reg_write32 (struct pci_io *pio, int reg, uint32_t data) +{ + ASSERT (pio != NULL); + ASSERT ((unsigned) reg < pio->size); + + if (pio->type == PCI_IO_MEM) + { + *((uint32_t *) (pio->addr.ptr + reg)) = data; + } + else if (pio->type == PCI_IO_PORT) + { + outl (pio->addr.port + reg, data); + } + else + PANIC ("pci: Invalid IO type\n"); +} + +void +pci_reg_write16 (struct pci_io *pio, int reg, uint16_t data) +{ + ASSERT (pio != NULL); + ASSERT ((unsigned) reg < pio->size); + + if (pio->type == PCI_IO_MEM) + { + *((uint16_t *) (pio->addr.ptr + reg)) = data; + } + else if (pio->type == PCI_IO_PORT) + { + outw (pio->addr.port + reg, data); + } + else + PANIC ("pci: Invalid IO type\n"); +} + +void +pci_reg_write8 (struct pci_io *pio, int reg, uint8_t data) +{ + ASSERT (pio != NULL); + ASSERT ((unsigned) reg < pio->size); + + if (pio->type == PCI_IO_MEM) + { + *((uint8_t *) (pio->addr.ptr + reg)) = data; + } + else if (pio->type == PCI_IO_PORT) + { + outb (pio->addr.port + reg, data); + } + else + PANIC ("pci: Invalid IO type\n"); +} + +uint32_t +pci_reg_read32 (struct pci_io *pio, int reg) +{ + uint32_t ret; + + ASSERT (pio != NULL); + ASSERT ((unsigned) reg < pio->size); + + if (pio->type == PCI_IO_MEM) + { + ret = *((uint32_t *) (pio->addr.ptr + reg)); + } + else if (pio->type == PCI_IO_PORT) + { + ret = inl (pio->addr.port + reg); + } + else + PANIC ("pci: Invalid IO type\n"); + + return ret; +} + +uint16_t +pci_reg_read16 (struct pci_io * pio, int reg) +{ + uint16_t ret; + + ASSERT (pio != NULL); + ASSERT ((unsigned) reg < pio->size); + + ret = 0; + if (pio->type == PCI_IO_MEM) + { + ret = *((uint16_t *) (pio->addr.ptr + reg)); + } + else if (pio->type == PCI_IO_PORT) + { + ret = inw (pio->addr.port + reg); + } + else + PANIC ("pci: Invalid IO type\n"); + + return ret; + +} + +uint8_t +pci_reg_read8 (struct pci_io * pio, int reg) +{ + uint8_t ret; + + ASSERT (pio != NULL); + ASSERT ((unsigned) reg < pio->size); + + if (pio->type == PCI_IO_MEM) + { + ret = *((uint8_t *) (pio->addr.ptr + reg)); + } + else if (pio->type == PCI_IO_PORT) + { + ret = inb (pio->addr.port + reg); + } + else + PANIC ("pci: Invalid IO type\n"); + + return ret; +} + +void +pci_read_in (struct pci_io *pio UNUSED, int off UNUSED, size_t size UNUSED, + void *buf UNUSED) +{ + PANIC ("pci_read_in: STUB"); +} + +void +pci_write_out (struct pci_io *pio UNUSED, int off UNUSED, size_t size UNUSED, + const void *buf UNUSED) +{ + PANIC ("pci_write_out: STUB"); +} + +static void +pci_write_config (int bus, int dev, int func, int reg, + int size, uint32_t data) +{ + int config_offset; + + config_offset = pci_config_offset (bus, dev, func, reg); + + outl (PCI_REG_ADDR, config_offset); + + switch (size) + { + case 1: + outb (PCI_REG_DATA + (reg & 3), data); + break; + + case 2: + outw (PCI_REG_DATA + (reg & 3), data); + break; + + case 4: + outl (PCI_REG_DATA, data); + break; + } +} + +static uint32_t +pci_read_config (int bus, int dev, int func, int reg, int size) +{ + uint32_t ret; + int config_offset; + + config_offset = pci_config_offset (bus, dev, func, reg); + + outl (PCI_REG_ADDR, config_offset); + + switch (size) + { + case 1: + ret = inb (PCI_REG_DATA); + break; + case 2: + ret = inw (PCI_REG_DATA); + break; + case 4: + ret = inl (PCI_REG_DATA); + break; + default: + PANIC ("pci: Strange config read size\n"); + } + + return ret; +} + +/* read entire configuration header into memory */ +static void +pci_read_all_config (int bus, int dev, int func, + struct pci_config_header *pch) +{ + unsigned int i; + for (i = 0; i < ((sizeof (struct pci_config_header) + 3) & ~3) / 4; i++) + { + ((uint32_t *) pch)[i] = pci_read_config (bus, dev, func, i * 4, 4); + } +} + + +/** scan PCI bus for all devices */ +static int +pci_scan_bus (int bus) +{ + int dev; + int max_bus; + + max_bus = 0; + + for (dev = 0; dev < PCI_MAX_DEV_PER_BUS; dev++) + { + struct pci_config_header pch; + int func_cnt, func; + + pci_read_all_config (bus, dev, 0, &pch); + + if (pch.pci_vendor_id == PCI_VENDOR_INVALID) + continue; + + func_cnt = 8; + if (!(pch.pci_header & PCI_HEADER_MULTIFUNC)) + { + func_cnt = 1; + } + + for (func = 0; func < func_cnt; func++) + { + int retbus; + retbus = pci_probe (bus, dev, func, &pch); + if (retbus > max_bus) + max_bus = retbus; + } + } + + return max_bus; +} + +/* get all information for a PCI device given bus/dev/func + add pci device to device list if applicable + return a new bus number if new bus is found + */ +static int +pci_probe (int bus, int dev, int func, struct pci_config_header *ph) +{ + struct pci_dev *pd; + + if (func != 0) + { + pci_read_all_config (bus, dev, func, ph); + if (ph->pci_vendor_id == PCI_VENDOR_INVALID) + return bus; + } + + pd = malloc (sizeof (struct pci_dev)); + memcpy (&pd->pch, ph, sizeof (struct pci_config_header)); + pd->irq_handler = NULL; + pd->irq_handler_aux = NULL; + pd->bus = bus; + pd->dev = dev; + pd->func = func; + + list_init (&pd->io_ranges); + list_push_back (&devices, &pd->peer); + + + if (ph->pci_major == PCI_MAJOR_BRIDGE) + { + if (ph->pci_minor == PCI_MINOR_PCI) + return pci_pci_bridge (pd); + } + + pci_setup_io (pd); + pci_power_on (pd); + + return bus; +} + +static void +pci_setup_io (struct pci_dev *pd) +{ + int i; + for (i = 0; i < PCI_BASE_COUNT; i++) + { + uint32_t tmp; + struct pci_io *pio; + + if (pd->pch.pci_base_reg[i] == 0) + { + continue; + } + + /* determine io granularity.. */ + pci_write_config (pd->bus, pd->dev, pd->func, + offsetof (struct pci_config_header, pci_base_reg[i]), + 4, ~0); + + tmp = + pci_read_config (pd->bus, pd->dev, pd->func, + offsetof (struct pci_config_header, pci_base_reg[i]), + 4); + + /* configure BAR to the default */ + pci_write_config (pd->bus, pd->dev, pd->func, + offsetof (struct pci_config_header, pci_base_reg[i]), + 4, pd->pch.pci_base_reg[i]); + + pio = malloc (sizeof (struct pci_io)); + pio->dev = pd; + + if (tmp & PCI_BASEADDR_IO) + { + pio->type = PCI_IO_PORT; + pio->size = (uint16_t) ((~tmp + 1) & 0xffff) + 1; + pio->addr.port = pd->pch.pci_base_reg[i] & ~1; + } + else + { + uint32_t ofs; + + pio->type = PCI_IO_MEM; + pio->size = ROUND_UP ((~tmp + 1), PGSIZE); + ofs = (pd->pch.pci_base_reg[i] & 0xfffffff0 & PGMASK); + pio->addr.ptr = pci_alloc_mem ((void *) pd->pch.pci_base_reg[i], + pio->size / PGSIZE); + if (pio->addr.ptr == NULL) + { + printf ("PCI: %d pages for %d:%d.%d failed - may not work\n", + pio->size / PGSIZE, pd->bus, pd->dev, pd->func); + free (pio); + pio = NULL; + } + else + { + pio->addr.ptr = (void *) ((uintptr_t) pio->addr.ptr + ofs); + } + } + + /* add IO struct to device, if valid */ + if (pio != NULL) + list_push_back (&pd->io_ranges, &pio->peer); + } + +} + +static void +pci_power_on (struct pci_dev *pd UNUSED) +{ + /* STUB */ +} + +static int +pci_pci_bridge (struct pci_dev *pd) +{ + int max_bus; + uint16_t command; + + /* put bus into offline mode */ + command = pd->pch.pci_command; + command &= ~0x3; + pci_write_config (pd->bus, pd->dev, pd->func, + offsetof (struct pci_config_header, pci_command), + 2, command); + pd->pch.pci_command = command; + + /* set up primary bus */ + pci_write_config (pd->bus, pd->dev, pd->func, 0x18, 1, pd->bus); + /* secondary bus */ + pci_write_config (pd->bus, pd->dev, pd->func, 0x19, 1, pd->bus + 1); + + /* disable subordinates */ + pci_write_config (pd->bus, pd->dev, pd->func, 0x1a, 1, 0xff); + + /* scan this new bus */ + max_bus = pci_scan_bus (pd->bus + 1); + + /* set subordinate to the actual last bus */ + pci_write_config (pd->bus, pd->dev, pd->func, 0x1a, 1, max_bus); + + /* put online */ + command |= 0x03; + pci_write_config (pd->bus, pd->dev, pd->func, + offsetof (struct pci_config_header, pci_command), + 2, command); + pd->pch.pci_command = command; + + return max_bus; +} + +/* alert all PCI devices waiting on interrupt line that IRQ fired */ +static void +pci_interrupt (struct intr_frame *frame) +{ + struct list_elem *e; + int int_line; + + int_line = frame->vec_no - 0x20; + e = list_begin (&int_devices); + while (e != list_end (&int_devices)) + { + struct pci_dev *pd; + + pd = list_entry (e, struct pci_dev, int_peer); + if (pd->pch.pci_int_line == int_line) + pd->irq_handler (pd->irq_handler_aux); + e = list_next (e); + } +} + +/* display information on all USB devices */ +void +pci_print_stats (void) +{ + struct list_elem *e; + + e = list_begin (&devices); + while (e != list_end (&devices)) + { + struct pci_dev *pd; + + pd = list_entry (e, struct pci_dev, peer); + pci_print_dev_info (pd); + + e = list_next (e); + } +} + +static void +pci_print_dev_info (struct pci_dev *pd) +{ + printf ("PCI Device %d:%d.%d (%x,%x): %s - %s (%s) IRQ %d\n", + pd->bus, pd->dev, pd->func, + pd->pch.pci_vendor_id, + pd->pch.pci_device_id, + pci_lookup_vendor (pd->pch.pci_vendor_id), + pci_lookup_device (pd->pch.pci_vendor_id, pd->pch.pci_device_id), + pci_lookup_class (pd->pch.pci_major, pd->pch.pci_minor, + pd->pch.pci_interface), pd->pch.pci_int_line); +} + +void +pci_mask_irq (struct pci_dev *pd) +{ + intr_irq_mask (pd->pch.pci_int_line); +} + +void +pci_unmask_irq (struct pci_dev *pd) +{ + intr_irq_unmask (pd->pch.pci_int_line); +} + +void +pci_write_config16 (struct pci_dev *pd, int off, uint16_t data) +{ + pci_write_config (pd->bus, pd->dev, pd->func, off, 2, data); +} + +void +pci_write_config32 (struct pci_dev *pd, int off, uint32_t data) +{ + pci_write_config (pd->bus, pd->dev, pd->func, off, 4, data); +} + +void +pci_write_config8 (struct pci_dev *pd, int off, uint8_t data) +{ + pci_write_config (pd->bus, pd->dev, pd->func, off, 1, data); +} + +uint8_t +pci_read_config8 (struct pci_dev *pd, int off) +{ + return pci_read_config (pd->bus, pd->dev, pd->func, off, 1); +} + +uint16_t +pci_read_config16 (struct pci_dev * pd, int off) +{ + return pci_read_config (pd->bus, pd->dev, pd->func, off, 2); +} + +uint32_t +pci_read_config32 (struct pci_dev * pd, int off) +{ + return pci_read_config (pd->bus, pd->dev, pd->func, off, 4); +} + + +/** allocate PCI memory pages for PCI devices */ +static void * +pci_alloc_mem (void *phys_ptr, int pages) +{ + void *vaddr; + int i; + + phys_ptr = (void *) ((uintptr_t) phys_ptr & ~PGMASK); + + /* not enough space to allocate? */ + if ((unsigned) (num_pci_pages + pages) >= (unsigned) PCI_ADDR_ZONE_PAGES) + { + return NULL; + } + + /* insert into PCI_ZONE */ + for (i = 0; i < pages; i++) + { + uint32_t pte_idx = (num_pci_pages + i) % 1024; + uint32_t pde_idx = (num_pci_pages + i) / 1024; + uint32_t *pt; + uint32_t pte; + + pde_idx += pd_no ((void *) PCI_ADDR_ZONE_BEGIN); + pte = ((uint32_t) phys_ptr + (i * PGSIZE)) | PTE_P | PTE_W | PTE_CD; + pt = (uint32_t *) (ptov (base_page_dir[pde_idx] & ~PGMASK)); + pt[pte_idx] = pte; + } + + vaddr = (void *) (PCI_ADDR_ZONE_BEGIN + (num_pci_pages * PGSIZE)); + num_pci_pages += pages; + + return vaddr; +} diff --git a/src/devices/pci.h b/src/devices/pci.h new file mode 100755 index 0000000..258c022 --- /dev/null +++ b/src/devices/pci.h @@ -0,0 +1,112 @@ +#ifndef DEVICES_PCI_H +#define DEVICES_PCI_H + +#include +#include + +/**************************/ +/* some major/minor pairs */ +/**************************/ +#define PCI_MAJOR_EARLY 0 +#define PCI_MINOR_VGA 1 + +#define PCI_MAJOR_MASS_STORAGE 1 /* mass storage controller */ +#define PCI_MINOR_SCSI 0 +#define PCI_MINOR_IDE 1 +#define PCI_MINOR_FLOPPY 2 +#define PCI_MINOR_IPI 3 +#define PCI_MINOR_RAID 4 +#define PCI_MINOR_MS_OTHER 0x80 + +#define PCI_MAJOR_NETWORK 2 /* network controller */ +#define PCI_MINOR_ETHERNET 0 +#define PCI_MINOR_TOKENRING 1 +#define PCI_MINOR_FDDI 2 +#define PCI_MINOR_ATM 3 +#define PCI_MINOR_ISDN 4 +#define PCI_MINOR_NET_OTHER 0x80 + +#define PCI_MAJOR_DISPLAY 3 /* display controller */ +#define PCI_MAJOR_MULTIMEDIA 4 /* multimedia device */ +#define PCI_MAJOR_MEMORY 5 /* memory controller */ + +#define PCI_MAJOR_BRIDGE 6 /* bus bridge controller */ +#define PCI_MINOR_HOST 0 +#define PCI_MINOR_ISA 1 +#define PCI_MINOR_EISA 2 +#define PCI_MINOR_MCA 3 +#define PCI_MINOR_PCI 4 +#define PCI_MINOR_PCMCIA 5 +#define PCI_MINOR_NUBUS 6 +#define PCI_MINOR_CARDBUS 7 +#define PCI_MINOR_RACEWAY 8 + +#define PCI_MAJOR_SIMPLE_COMM 7 + +#define PCI_MAJOR_BASE_PERIPHERAL 8 +#define PCI_MINOR_PIC 0 +#define PCI_MINOR_DMA 1 +#define PCI_MINOR_TIMER 2 +#define PCI_MINOR_RTC 3 +#define PCI_MINOR_HOTPLUG 4 + + +#define PCI_MAJOR_INPUT 9 +#define PCI_MAJOR_DOCK 10 +#define PCI_MAJOR_PROCESSOR 11 +#define PCI_MINOR_386 0 +#define PCI_MINOR_486 1 +#define PCI_MINOR_PENTIUM 2 +#define PCI_MINOR_ALPHA 0x10 +#define PCI_MINOR_PPC 0x20 +#define PCI_MINOR_MIPS 0x30 +#define PCI_MINOR_COPROC 0x40 + +#define PCI_MAJOR_SERIALBUS 12 +#define PCI_MINOR_FIREWIRE 0 +#define PCI_MINOR_ACCESS 1 +#define PCI_MINOR_SSA 2 +#define PCI_MINOR_USB 3 +#define PCI_USB_IFACE_UHCI 0 +#define PCI_USB_IFACE_OHCI 0x10 +#define PCI_USB_IFACE_EHCI 0x20 +#define PCI_MINOR_FIBRE 4 +#define PCI_MAJOR_UNDEF 0xff + +typedef void pci_handler_func (void *AUX); + +/* used for io range for a pci device */ +struct pci_io; + +/* structure representing a specific pci device/function */ +struct pci_dev; + +void pci_init (void); +struct pci_dev *pci_get_device (int vendor, int device, int func, int n); +struct pci_dev *pci_get_dev_by_class (int major, int minor, int iface, int n); +struct pci_io *pci_io_enum (struct pci_dev *, struct pci_io *last); +void pci_register_irq (struct pci_dev *, pci_handler_func *, void *AUX); +void pci_unregister_irq (struct pci_dev *); +size_t pci_io_size (struct pci_io *); + +void pci_write_config8 (struct pci_dev *, int reg, uint8_t); +void pci_write_config16 (struct pci_dev *, int reg, uint16_t); +void pci_write_config32 (struct pci_dev *, int reg, uint32_t); +uint8_t pci_read_config8 (struct pci_dev *, int reg); +uint16_t pci_read_config16 (struct pci_dev *, int reg); +uint32_t pci_read_config32 (struct pci_dev *, int reg); + +void pci_reg_write32 (struct pci_io *, int reg, uint32_t); +void pci_reg_write16 (struct pci_io *, int reg, uint16_t); +void pci_reg_write8 (struct pci_io *, int reg, uint8_t); +uint32_t pci_reg_read32 (struct pci_io *, int reg); +uint16_t pci_reg_read16 (struct pci_io *, int reg); +uint8_t pci_reg_read8 (struct pci_io *, int reg); + +void pci_read_in (struct pci_io *, int off, size_t sz, void *buf); +void pci_write_out (struct pci_io *, int off, size_t sz, const void *buf); +void pci_print_stats (void); +void pci_mask_irq (struct pci_dev *); +void pci_unmask_irq (struct pci_dev *); + +#endif diff --git a/src/devices/pci_lookup.h b/src/devices/pci_lookup.h new file mode 100644 index 0000000..355f24b --- /dev/null +++ b/src/devices/pci_lookup.h @@ -0,0 +1,138 @@ +#ifndef PCILOOKUP_H +#define PCILOOKUP_H + +#ifdef PCI_TRANSLATION_ENABLE + +struct pci_vendor +{ + uint16_t vendor_id; + char *vendor; +}; + +struct pci_vendor pci_vendor_table[] = { + {0x1013, "Cirrus Logic"}, + {0x10de, "nVidia"}, + {0x10EC, "Realtek"}, + {0x11c1, "Agere Systems"}, + {0x8086, "Intel"} +}; + +struct pci_device +{ + uint16_t vendor_id; + uint16_t device_id; + char *device; +}; + +struct pci_device pci_device_table[] = { + {0x1013, 0x00b8, "CL-GD5446"}, + {0x10ec, 0x8029, "RTL8029 - NE2K Compatible"}, + {0x10ec, 0x8139, "RTL8139"}, + {0x8086, 0x1237, "82441FX"}, + {0x8086, 0x7000, "82371SB_ISA"}, + {0x8086, 0x7010, "82371SB_IDE"}, + {0x8086, 0x7020, "82371SB_USB"}, + {0x8086, 0x7113, "82371AB/EB/MB_ACPI"} +}; + + +#define PCI_DEVICE_TABLE_SZ (sizeof (pci_device_table) / sizeof (struct pci_device )) +#define PCI_VENDOR_TABLE_SZ (sizeof (pci_vendor_table) / sizeof (struct pci_vendor)) + +/** Too lazy to to a binary search... */ + +const char *pci_lookup_vendor (uint16_t vendor); +const char *pci_lookup_device (uint16_t vendor, uint16_t device); + +const char * +pci_lookup_vendor (uint16_t vendor) +{ + unsigned int i; + for (i = 0; i < PCI_VENDOR_TABLE_SZ; i++) + { + if (pci_vendor_table[i].vendor_id > vendor) + break; + if (pci_vendor_table[i].vendor_id == vendor) + return pci_vendor_table[i].vendor; + } + + return "Unknown Vendor"; +} + +const char * +pci_lookup_device (uint16_t vendor, uint16_t device) +{ + unsigned int i; + for (i = 0; i < PCI_DEVICE_TABLE_SZ; i++) + { + if (pci_device_table[i].vendor_id > vendor) + break; + if (pci_device_table[i].vendor_id == vendor && + pci_device_table[i].device_id == device) + return pci_device_table[i].device; + } + + return "Unknown Device"; +} + +#else + +#define pci_lookup_vendor(x) "Unknown Vendor" +#define pci_lookup_device(x,y) "Unknown Device" + +#endif + +struct pci_class +{ + uint8_t major; + uint8_t minor; + uint8_t iface; + char *name; +}; + +struct pci_class pci_class_table[] = { + {0, 0, 0, "Pre-PCI 2.0 Non-VGA Device"}, + {0, 1, 0, "Pre-PCI 2.0 VGA Device"}, + {1, 0, 0, "SCSI Controller"}, + {1, 1, 0, "IDE Controller"}, + {1, 2, 0, "Floppy Disk Controller"}, + {2, 0, 0, "Ethernet"}, + {3, 0, 0, "VGA Controller"}, + {3, 1, 0, "XGA Controller"}, + {5, 0, 0, "Memory Controller - RAM"}, + {5, 1, 0, "Memory Controller - Flash"}, + {6, 0, 0, "PCI Host"}, + {6, 1, 0, "PCI-ISA Bridge"}, + {6, 2, 0, "PCI-EISA Bridge"}, + {6, 4, 0, "PCI-PCI Bridge"}, + {6, 5, 0, "PCI-PCMCIA Bridge"}, + {12, 0, 0, "Firewire Adapter"}, + {12, 3, 0, "USB 1.1 Controller (UHCI)"}, + {12, 3, 0x10, "USB 1.1 Controller (OHCI)"}, + {12, 3, 0x20, "USB 2.0 Controller (EHCI)"} +}; + +#define PCI_CLASS_TABLE_SZ (sizeof(pci_class_table) / sizeof(struct pci_class)) + +const char *pci_lookup_class (uint8_t major, uint8_t minor, uint8_t iface); +const char * +pci_lookup_class (uint8_t major, uint8_t minor, uint8_t iface) +{ + unsigned int i; + for (i = 0; i < PCI_CLASS_TABLE_SZ; i++) + { + if (pci_class_table[i].major > major) + break; + if (pci_class_table[i].major != major) + continue; + if (pci_class_table[i].minor != minor) + continue; + if (pci_class_table[i].iface != iface) + continue; + return pci_class_table[i].name; + } + + return "Unknown Type"; +} + +#endif diff --git a/src/devices/pit.c b/src/devices/pit.c new file mode 100644 index 0000000..bfb1889 --- /dev/null +++ b/src/devices/pit.c @@ -0,0 +1,83 @@ +#include "devices/pit.h" +#include +#include +#include "threads/interrupt.h" +#include "threads/io.h" + +/* Interface to 8254 Programmable Interrupt Timer (PIT). + Refer to [8254] for details. */ + +/* 8254 registers. */ +#define PIT_PORT_CONTROL 0x43 /* Control port. */ +#define PIT_PORT_COUNTER(CHANNEL) (0x40 + (CHANNEL)) /* Counter port. */ + +/* PIT cycles per second. */ +#define PIT_HZ 1193180 + +/* Configure the given CHANNEL in the PIT. In a PC, the PIT's + three output channels are hooked up like this: + + - Channel 0 is connected to interrupt line 0, so that it can + be used as a periodic timer interrupt, as implemented in + Pintos in devices/timer.c. + + - Channel 1 is used for dynamic RAM refresh (in older PCs). + No good can come of messing with this. + + - Channel 2 is connected to the PC speaker, so that it can + be used to play a tone, as implemented in Pintos in + devices/speaker.c. + + MODE specifies the form of output: + + - Mode 2 is a periodic pulse: the channel's output is 1 for + most of the period, but drops to 0 briefly toward the end + of the period. This is useful for hooking up to an + interrupt controller to generate a periodic interrupt. + + - Mode 3 is a square wave: for the first half of the period + it is 1, for the second half it is 0. This is useful for + generating a tone on a speaker. + + - Other modes are less useful. + + FREQUENCY is the number of periods per second, in Hz. */ +void +pit_configure_channel (int channel, int mode, int frequency) +{ + uint16_t count; + enum intr_level old_level; + + ASSERT (channel == 0 || channel == 2); + ASSERT (mode == 2 || mode == 3); + + /* Convert FREQUENCY to a PIT counter value. The PIT has a + clock that runs at PIT_HZ cycles per second. We must + translate FREQUENCY into a number of these cycles. */ + if (frequency < 19) + { + /* Frequency is too low: the quotient would overflow the + 16-bit counter. Force it to 0, which the PIT treats as + 65536, the highest possible count. This yields a 18.2 + Hz timer, approximately. */ + count = 0; + } + else if (frequency > PIT_HZ) + { + /* Frequency is too high: the quotient would underflow to + 0, which the PIT would interpret as 65536. A count of 1 + is illegal in mode 2, so we force it to 2, which yields + a 596.590 kHz timer, approximately. (This timer rate is + probably too fast to be useful anyhow.) */ + count = 2; + } + else + count = (PIT_HZ + frequency / 2) / frequency; + + /* Configure the PIT mode and load its counters. */ + old_level = intr_disable (); + outb (PIT_PORT_CONTROL, (channel << 6) | 0x30 | (mode << 1)); + outb (PIT_PORT_COUNTER (channel), count); + outb (PIT_PORT_COUNTER (channel), count >> 8); + intr_set_level (old_level); +} diff --git a/src/devices/pit.h b/src/devices/pit.h new file mode 100644 index 0000000..dff36ae --- /dev/null +++ b/src/devices/pit.h @@ -0,0 +1,8 @@ +#ifndef DEVICES_PIT_H +#define DEVICES_PIT_H + +#include + +void pit_configure_channel (int channel, int mode, int frequency); + +#endif /* devices/pit.h */ diff --git a/src/devices/rtc.c b/src/devices/rtc.c new file mode 100644 index 0000000..d99eb46 --- /dev/null +++ b/src/devices/rtc.c @@ -0,0 +1,112 @@ +#include "devices/rtc.h" +#include +#include "threads/io.h" + +/* This code is an interface to the MC146818A-compatible real + time clock found on PC motherboards. See [MC146818A] for + hardware details. */ + +/* I/O register addresses. */ +#define CMOS_REG_SET 0x70 /* Selects CMOS register exposed by REG_IO. */ +#define CMOS_REG_IO 0x71 /* Contains the selected data byte. */ + +/* Indexes of CMOS registers with real-time clock functions. + Note that all of these registers are in BCD format, + so that 0x59 means 59, not 89. */ +#define RTC_REG_SEC 0 /* Second: 0x00...0x59. */ +#define RTC_REG_MIN 2 /* Minute: 0x00...0x59. */ +#define RTC_REG_HOUR 4 /* Hour: 0x00...0x23. */ +#define RTC_REG_MDAY 7 /* Day of the month: 0x01...0x31. */ +#define RTC_REG_MON 8 /* Month: 0x01...0x12. */ +#define RTC_REG_YEAR 9 /* Year: 0x00...0x99. */ + +/* Indexes of CMOS control registers. */ +#define RTC_REG_A 0x0a /* Register A: update-in-progress. */ +#define RTC_REG_B 0x0b /* Register B: 24/12 hour time, irq enables. */ +#define RTC_REG_C 0x0c /* Register C: pending interrupts. */ +#define RTC_REG_D 0x0d /* Register D: valid time? */ + +/* Register A. */ +#define RTCSA_UIP 0x80 /* Set while time update in progress. */ + +/* Register B. */ +#define RTCSB_SET 0x80 /* Disables update to let time be set. */ +#define RTCSB_DM 0x04 /* 0 = BCD time format, 1 = binary format. */ +#define RTCSB_24HR 0x02 /* 0 = 12-hour format, 1 = 24-hour format. */ + +static int bcd_to_bin (uint8_t); +static uint8_t cmos_read (uint8_t index); + +/* Returns number of seconds since Unix epoch of January 1, + 1970. */ +time_t +rtc_get_time (void) +{ + static const int days_per_month[12] = + { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 + }; + int sec, min, hour, mday, mon, year; + time_t time; + int i; + + /* Get time components. + + We repeatedly read the time until it is stable from one read + to another, in case we start our initial read in the middle + of an update. This strategy is not recommended by the + MC146818A datasheet, but it is simpler than any of their + suggestions and, furthermore, it is also used by Linux. + + The MC146818A can be configured for BCD or binary format, + but for historical reasons everyone always uses BCD format + except on obscure non-PC platforms, so we don't bother + trying to detect the format in use. */ + do + { + sec = bcd_to_bin (cmos_read (RTC_REG_SEC)); + min = bcd_to_bin (cmos_read (RTC_REG_MIN)); + hour = bcd_to_bin (cmos_read (RTC_REG_HOUR)); + mday = bcd_to_bin (cmos_read (RTC_REG_MDAY)); + mon = bcd_to_bin (cmos_read (RTC_REG_MON)); + year = bcd_to_bin (cmos_read (RTC_REG_YEAR)); + } + while (sec != bcd_to_bin (cmos_read (RTC_REG_SEC))); + + /* Translate years-since-1900 into years-since-1970. + If it's before the epoch, assume that it has passed 2000. + This will break at 2070, but that's long after our 31-bit + time_t breaks in 2038. */ + if (year < 70) + year += 100; + year -= 70; + + /* Break down all components into seconds. */ + time = (year * 365 + (year - 1) / 4) * 24 * 60 * 60; + for (i = 1; i <= mon; i++) + time += days_per_month[i - 1] * 24 * 60 * 60; + if (mon > 2 && year % 4 == 0) + time += 24 * 60 * 60; + time += (mday - 1) * 24 * 60 * 60; + time += hour * 60 * 60; + time += min * 60; + time += sec; + + return time; +} + +/* Returns the integer value of the given BCD byte. */ +static int +bcd_to_bin (uint8_t x) +{ + return (x & 0x0f) + ((x >> 4) * 10); +} + +/* Reads a byte from the CMOS register with the given INDEX and + returns the byte read. */ +static uint8_t +cmos_read (uint8_t index) +{ + outb (CMOS_REG_SET, index); + return inb (CMOS_REG_IO); +} diff --git a/src/devices/rtc.h b/src/devices/rtc.h new file mode 100644 index 0000000..96a822f --- /dev/null +++ b/src/devices/rtc.h @@ -0,0 +1,8 @@ +#ifndef RTC_H +#define RTC_H + +typedef unsigned long time_t; + +time_t rtc_get_time (void); + +#endif diff --git a/src/devices/speaker.c b/src/devices/speaker.c new file mode 100644 index 0000000..5052005 --- /dev/null +++ b/src/devices/speaker.c @@ -0,0 +1,68 @@ +#include "devices/speaker.h" +#include "devices/pit.h" +#include "threads/io.h" +#include "threads/interrupt.h" +#include "devices/timer.h" + +/* Speaker port enable I/O register. */ +#define SPEAKER_PORT_GATE 0x61 + +/* Speaker port enable bits. */ +#define SPEAKER_GATE_ENABLE 0x03 + +/* Sets the PC speaker to emit a tone at the given FREQUENCY, in + Hz. */ +void +speaker_on (int frequency) +{ + if (frequency >= 20 && frequency <= 20000) + { + /* Set the timer channel that's connected to the speaker to + output a square wave at the given FREQUENCY, then + connect the timer channel output to the speaker. */ + enum intr_level old_level = intr_disable (); + pit_configure_channel (2, 3, frequency); + outb (SPEAKER_PORT_GATE, inb (SPEAKER_PORT_GATE) | SPEAKER_GATE_ENABLE); + intr_set_level (old_level); + } + else + { + /* FREQUENCY is outside the range of normal human hearing. + Just turn off the speaker. */ + speaker_off (); + } +} + +/* Turn off the PC speaker, by disconnecting the timer channel's + output from the speaker. */ +void +speaker_off (void) +{ + enum intr_level old_level = intr_disable (); + outb (SPEAKER_PORT_GATE, inb (SPEAKER_PORT_GATE) & ~SPEAKER_GATE_ENABLE); + intr_set_level (old_level); +} + +/* Briefly beep the PC speaker. */ +void +speaker_beep (void) +{ + /* Only attempt to beep the speaker if interrupts are enabled, + because we don't want to freeze the machine during the beep. + We could add a hook to the timer interrupt to avoid that + problem, but then we'd risk failing to ever stop the beep if + Pintos crashes for some unrelated reason. There's nothing + more annoying than a machine whose beeping you can't stop + without a power cycle. + + We can't just enable interrupts while we sleep. For one + thing, we get called (indirectly) from printf, which should + always work, even during boot before we're ready to enable + interrupts. */ + if (intr_get_level () == INTR_ON) + { + speaker_on (440); + timer_msleep (250); + speaker_off (); + } +} diff --git a/src/devices/speaker.h b/src/devices/speaker.h new file mode 100644 index 0000000..98cef7b --- /dev/null +++ b/src/devices/speaker.h @@ -0,0 +1,8 @@ +#ifndef DEVICES_SPEAKER_H +#define DEVICES_SPEAKER_H + +void speaker_on (int frequency); +void speaker_off (void); +void speaker_beep (void); + +#endif /* devices/speaker.h */ diff --git a/src/devices/timer.c b/src/devices/timer.c index a4521de..a83a588 100644 --- a/src/devices/timer.c +++ b/src/devices/timer.c @@ -3,8 +3,8 @@ #include #include #include +#include "devices/pit.h" #include "threads/interrupt.h" -#include "threads/io.h" #include "threads/synch.h" #include "threads/thread.h" @@ -29,20 +29,12 @@ static bool too_many_loops (unsigned loops); static void busy_wait (int64_t loops); static void real_time_sleep (int64_t num, int32_t denom); -/* Sets up the 8254 Programmable Interval Timer (PIT) to - interrupt PIT_FREQ times per second, and registers the - corresponding interrupt. */ +/* Sets up the timer to interrupt TIMER_FREQ times per second, + and registers the corresponding interrupt. */ void timer_init (void) { - /* 8254 input frequency divided by TIMER_FREQ, rounded to - nearest. */ - uint16_t count = (1193180 + TIMER_FREQ / 2) / TIMER_FREQ; - - outb (0x43, 0x34); /* CW: counter 0, LSB then MSB, mode 2, binary. */ - outb (0x40, count & 0xff); - outb (0x40, count >> 8); - + pit_configure_channel (0, 2, TIMER_FREQ); intr_register_ext (0x20, timer_interrupt, "8254 Timer"); } diff --git a/src/devices/usb.c b/src/devices/usb.c new file mode 100644 index 0000000..f4bc738 --- /dev/null +++ b/src/devices/usb.c @@ -0,0 +1,810 @@ +#include "devices/usb.h" +#include "devices/usb_hub.h" +#include "threads/synch.h" +#include "threads/malloc.h" +#include "devices/timer.h" +#include +#include +#include + +#define ADDRS_PER_HOST 127 /* number of configurable addresses */ +#define ADDR_DEFAULT 0 /* default configuration address */ +#define ADDR_FIRST 1 /* first configurable address */ + +/** sometimes used for ''value' */ +/* used in desc_header */ +#define SETUP_DESC_DEVICE 1 +#define SETUP_DESC_CONFIG 2 +#define SETUP_DESC_STRING 3 +#define SETUP_DESC_IFACE 4 +#define SETUP_DESC_ENDPOINT 5 +#define SETUP_DESC_DEVQUAL 6 +#define SETUP_DESC_SPDCONFIG 7 +#define SETUP_DESC_IFACEPWR 8 + +#pragma pack(1) +struct desc_header +{ + uint8_t length; + uint8_t type; +}; + +/** + * More details can be found in chapter 9 of the usb1.1 spec + */ + +struct device_descriptor +{ + struct desc_header hdr; + uint16_t usb_spec; + uint8_t dev_class; + uint8_t dev_subclass; + uint8_t dev_proto; + uint8_t max_pktsz; + + uint16_t vendor_id; + uint16_t product_id; + uint16_t device_id; + uint8_t manufacturer; + uint8_t product; + uint8_t serial; + uint8_t num_configs; +}; + +struct device_qualifier +{ + uint16_t usb_spec; + uint8_t dev_class; + uint8_t dev_subclass; + uint8_t dev_proto; + uint8_t max_pktsz; + uint8_t num_configs; + uint8_t resv; +}; + +struct config_descriptor +{ + struct desc_header hdr; + uint16_t total_length; + uint8_t num_ifaces; + uint8_t config_val; + uint8_t config_desc; + uint8_t attributes; + uint8_t max_power; +}; + +struct interface_descriptor +{ + struct desc_header hdr; + uint8_t iface_num; + uint8_t alt_setting; + uint8_t num_endpoints; + uint8_t iface_class; + uint8_t iface_subclass; + uint8_t iface_proto; + uint8_t iface_desc; +}; + +struct endpoint_descriptor +{ + struct desc_header hdr; + /* end point address */ + uint8_t endpoint_num:4; + uint8_t resv1:3; + uint8_t direction:1; + + /* attributes */ + uint8_t transfer:2; + uint8_t synch:2; + uint8_t usage:2; + uint8_t resv2:2; + + uint16_t max_pktsz; + uint8_t interval; + +}; + +#pragma pack() + +#define USB_CLASS_HUB 0x09 +struct host +{ + struct usb_host *dev; /* host driver */ + host_info info; /* private to host */ + struct bitmap *usb_dev_addrs; /* addrs used on device */ + struct list host_usb_devs; /* usb devices on host */ + struct list_elem peers; /* other hosts on system */ +}; + +struct class +{ + struct usb_class *dev; /* class driver */ + struct list usb_ifaces; /* usb devices on class */ + struct list_elem peers; +}; + +static struct list usb_dev_list; /* list of all usb devices */ +static struct list class_list; /* list of all classes */ +static struct list host_list; /* list of all hosts on system */ +static struct lock usb_sys_lock; /* big usb lock */ + +#define usb_lock() lock_acquire(&usb_sys_lock) +#define usb_unlock() lock_release(&usb_sys_lock) + +static void usb_scan_devices (struct host *h); +static struct usb_dev *usb_configure_default (struct host *h); +static char *usb_get_string (struct usb_dev *d, int ndx); +static struct class *usb_get_class_by_id (int id); +static void usb_setup_dev_addr (struct usb_dev *dev); +static void usb_config_dev (struct usb_dev *dev, int config_val); +static int usb_load_config (struct usb_dev *dev, int idx, void *data, + int dsz); +static int usb_tx_all (struct usb_endpoint *eop, void *buf, + int max_bytes, int bailout, bool in); +static void usb_attach_interfaces (struct usb_dev *dev); +static void usb_apply_class_to_interfaces (struct class *c); +static size_t wchar_to_ascii (char *dst, const char *src); + +extern void uhci_init (void); +extern void ehci_init (void); + +void +usb_init (void) +{ + list_init (&host_list); + list_init (&class_list); + list_init (&usb_dev_list); + lock_init (&usb_sys_lock); + + usb_hub_init (); + + /* add usb hosts */ + printf ("Initializing EHCI\n"); + ehci_init (); + printf ("Initializing UHCI\n"); + uhci_init (); +} + +/* add host controller device to usb layer */ +void +usb_register_host (struct usb_host *uh, host_info info) +{ + struct host *h; + + h = malloc (sizeof (struct host)); + h->dev = uh; + h->info = info; + h->usb_dev_addrs = bitmap_create (ADDRS_PER_HOST); + list_init (&h->host_usb_devs); + + usb_lock (); + list_push_back (&host_list, &h->peers); + usb_unlock (); + + usb_scan_devices (h); +} + +int +usb_register_class (struct usb_class *uc) +{ + struct class *c; + + usb_lock (); + + /* check to make sure class is not in list */ + if (usb_get_class_by_id (uc->class_id) != NULL) + { + usb_unlock (); + return -1; + } + + + /* add class to class list */ + c = malloc (sizeof (struct class)); + c->dev = uc; + list_init (&c->usb_ifaces); + list_push_back (&class_list, &c->peers); + + usb_apply_class_to_interfaces (c); + + usb_unlock (); + + return 0; +} + +static void +usb_scan_devices (struct host *h) +{ + /* scan until there all devices using default pipe are configured */ + + printf ("USB: scanning devices...\n"); + while (1) + { + struct usb_dev *dev; + + dev = usb_configure_default (h); + if (dev == NULL) + break; + + if (dev->ignore_device == false) + printf ("USB Device %d: %s (%s)\n", + dev->addr, dev->product, dev->manufacturer); + + list_push_back (&h->host_usb_devs, &dev->host_peers); + list_push_back (&usb_dev_list, &dev->sys_peers); + usb_attach_interfaces (dev); + } +} + +static int +send_status (struct host *h, host_eop_info cfg_eop) +{ + int sz; + int err; + + h->dev->set_toggle (cfg_eop, 1); + sz = 0; + err = h->dev->tx_pkt (cfg_eop, USB_TOKEN_OUT, NULL, 0, 0, &sz, true); + if (err) + printf ("error %d in status transaction\n", err); + return err; +} + +static struct usb_dev * +usb_configure_default (struct host *h) +{ + struct usb_dev *dev; + struct usb_setup_pkt sp; + char data[256]; + struct device_descriptor *dd; + host_dev_info hi; + host_eop_info cfg_eop; + bool ignore_device = false; + int err, sz, txed; + int config_val; + + hi = h->dev->create_dev_channel (h->info, ADDR_DEFAULT, USB_VERSION_1_1); + cfg_eop = h->dev->create_eop (hi, 0, 64); + + /* Get first 8 bytes of device descriptor. */ + sp.recipient = USB_SETUP_RECIP_DEV; + sp.type = USB_SETUP_TYPE_STD; + sp.direction = 1; + sp.request = REQ_STD_GET_DESC; + sp.value = SETUP_DESC_DEVICE << 8; + sp.index = 0; + sp.length = 8; + + err = + h->dev->tx_pkt (cfg_eop, USB_TOKEN_SETUP, &sp, 0, sizeof (sp), NULL, + true); + if (err != USB_HOST_ERR_NONE) + goto error; + + dd = (void *) &data; + memset (dd, 0, sizeof (data)); + txed = 0; + err = h->dev->tx_pkt (cfg_eop, USB_TOKEN_IN, data, 0, 8, &sz, true); + if (err != USB_HOST_ERR_NONE) + goto error; + + err = send_status (h, cfg_eop); + if (err != USB_HOST_ERR_NONE) + return NULL; + + if (dd->usb_spec == USB_VERSION_1_0) + { + /* USB 1.0 devices have strict schedule requirements not yet supported */ + printf ("USB 1.0 device detected - skipping\n"); + goto error; + } + + /* Now we know the max packet size. */ + if (dd->max_pktsz != 8 && dd->max_pktsz != 16 + && dd->max_pktsz != 32 && dd->max_pktsz != 64) + goto error; + + h->dev->remove_eop (cfg_eop); + cfg_eop = h->dev->create_eop (hi, 0, dd->max_pktsz); + + /* Get the whole descriptor. */ + sp.length = sizeof *dd; + err = + h->dev->tx_pkt (cfg_eop, USB_TOKEN_SETUP, &sp, 0, sizeof (sp), NULL, + true); + if (err != USB_HOST_ERR_NONE) + goto error; + + txed = 0; + while (txed < sizeof *dd) + { + err = h->dev->tx_pkt (cfg_eop, USB_TOKEN_IN, data + txed, 0, + sizeof *dd - txed, &sz, true); + if (err) + goto error; + txed += sz; + } + + err = send_status (h, cfg_eop); + if (err != USB_HOST_ERR_NONE) + return NULL; + + if (dd->num_configs == 0) + ignore_device = true; + + /* device exists - create device structure */ + dev = malloc (sizeof (struct usb_dev)); + memset (dev, 0, sizeof (struct usb_dev)); + list_init (&dev->interfaces); + + dev->ignore_device = ignore_device; + dev->h_dev = hi; + dev->h_cfg_eop = cfg_eop; + + dev->cfg_eop.h_eop = cfg_eop; + + dev->usb_version = dd->usb_spec; + dev->default_iface.class_id = dd->dev_class; + dev->default_iface.subclass_id = dd->dev_subclass; + dev->default_iface.iface_num = 0; + dev->default_iface.proto = dd->dev_proto; + dev->default_iface.class = NULL; + dev->default_iface.c_info = NULL; + dev->default_iface.dev = dev; + + dev->cfg_eop.iface = &dev->default_iface; + dev->cfg_eop.max_pkt = dd->max_pktsz; + dev->cfg_eop.eop = 0; + + dev->host = h; + + dev->vendor_id = dd->vendor_id; + dev->product_id = dd->product_id; + dev->device_id = dd->device_id; + + if (ignore_device == false) + { + dev->product = usb_get_string (dev, dd->product); + dev->manufacturer = usb_get_string (dev, dd->manufacturer); + } + + config_val = -123; + /* read in configuration data if there are configurations available */ + /* (which there should be...) */ + if (dd->num_configs > 0 && ignore_device == false) + { + config_val = usb_load_config (dev, 0, data, sizeof (data)); + if (config_val < 0) + { + printf + ("USB: Invalid configuration value %d on '%s (%s)' (%x,%x,%x)\n", + config_val, dev->product, dev->manufacturer, dev->vendor_id, + dev->product_id, dev->device_id); + } + } + + usb_setup_dev_addr (dev); + + usb_config_dev (dev, (config_val < 0) ? 1 : config_val); + + return dev; + + error: + h->dev->remove_eop (cfg_eop); + h->dev->remove_dev_channel (hi); + return NULL; +} + +/** + * Load in data for 'idx' configuration into device structure + * XXX support multiple configurations + */ +static int +usb_load_config (struct usb_dev *dev, int idx, void *data, int dsz) +{ + struct usb_setup_pkt sp; + struct config_descriptor *cd; + struct host *h; + host_eop_info cfg; + void *ptr; + int config_val, err, sz; + int i; + + h = dev->host; + cfg = dev->h_cfg_eop; + + sp.recipient = USB_SETUP_RECIP_DEV; + sp.type = USB_SETUP_TYPE_STD; + sp.direction = 1; + sp.request = REQ_STD_GET_DESC; + sp.value = SETUP_DESC_CONFIG << 8 | idx; + sp.index = 0; + sp.length = dsz; + cd = data; + + err = h->dev->tx_pkt (cfg, USB_TOKEN_SETUP, &sp, 0, sizeof (sp), &sz, true); + if (err != USB_HOST_ERR_NONE) + { + printf ("USB: Could not setup GET descriptor\n"); + return -err; + } + + sz = usb_tx_all (&dev->cfg_eop, cd, dsz, + sizeof (struct config_descriptor), true); + if (sz < sizeof (struct config_descriptor)) + { + printf ("USB: Did not rx GET descriptor (%d bytes, expected %d)\n", sz, + sizeof (struct config_descriptor)); + return -err; + } + + if (sz == 0 || cd->hdr.type != SETUP_DESC_CONFIG) + { + printf ("USB: Invalid descriptor\n"); + return -1; + } + + if (sz < cd->total_length) + sz += usb_tx_all (&dev->cfg_eop, data+sz, dsz, cd->total_length - sz, true); + + send_status (h, cfg); + + dev->pwr = cd->max_power; + + /* interface information comes right after config data */ + /* scan interfaces */ + ptr = data + sizeof (struct config_descriptor); + + for (i = 0; i < cd->num_ifaces; i++) + { + struct interface_descriptor *iface; + struct usb_iface *ui; + int j; + + iface = ptr; + if (iface->hdr.type != SETUP_DESC_IFACE) + { + hex_dump (0, iface, 64, false); + PANIC ("Expected %d, found %d\n", SETUP_DESC_IFACE, + iface->hdr.type); + } + + ui = malloc (sizeof (struct usb_iface)); + ui->class_id = iface->iface_class; + ui->subclass_id = iface->iface_subclass; + ui->iface_num = iface->iface_num; + ui->proto = iface->iface_proto; + ui->class = NULL; + ui->c_info = NULL; + ui->dev = dev; + list_init (&ui->endpoints); + + /* endpoint data comes after interfaces */ + /* scan endpoints */ + ptr += sizeof (struct interface_descriptor); + for (j = 0; j < iface->num_endpoints; + j++, ptr += sizeof (struct endpoint_descriptor)) + { + struct usb_endpoint *ue; + struct endpoint_descriptor *ed; + + ed = ptr; + + ue = malloc (sizeof (struct usb_endpoint)); + ue->eop = ed->endpoint_num; + ue->direction = ed->direction; + ue->attr = ed->transfer; + ue->max_pkt = ed->max_pktsz; + ue->interval = ed->interval; + ue->iface = ui; + ue->h_eop = h->dev->create_eop (dev->h_dev, ue->eop, ue->max_pkt); + + list_push_back (&ui->endpoints, &ue->peers); + } + + list_push_back (&dev->interfaces, &ui->peers); + } + + config_val = cd->config_val; + + return config_val; +} + +/** + * Set USB device configuration to desired configuration value + */ +static void +usb_config_dev (struct usb_dev *dev, int config_val) +{ + struct usb_setup_pkt sp; + struct host *h; + host_eop_info cfg; + int err; + + h = dev->host; + cfg = dev->h_cfg_eop; + + sp.recipient = USB_SETUP_RECIP_DEV; + sp.type = USB_SETUP_TYPE_STD; + sp.direction = 0; + sp.request = REQ_STD_SET_CONFIG; + sp.value = config_val; + sp.index = 0; + sp.length = 0; + err = h->dev->tx_pkt (cfg, USB_TOKEN_SETUP, &sp, 0, sizeof (sp), NULL, true); + if (err != USB_HOST_ERR_NONE) + PANIC ("USB: Config setup packet did not tx\n"); + + err = h->dev->tx_pkt (cfg, USB_TOKEN_IN, NULL, 0, 0, NULL, true); + if (err != USB_HOST_ERR_NONE) + PANIC ("USB: Could not configure device!\n"); +} + +/** + * Set a device address to something other than the default pipe + */ +static void +usb_setup_dev_addr (struct usb_dev *dev) +{ + struct usb_setup_pkt sp; + struct host *h; + host_eop_info cfg; + int err; + + ASSERT (dev->addr == 0); + + h = dev->host; + cfg = dev->h_cfg_eop; + + dev->addr = bitmap_scan_and_flip (h->usb_dev_addrs, 1, 1, false); + + sp.recipient = USB_SETUP_RECIP_DEV; + sp.type = USB_SETUP_TYPE_STD; + sp.direction = 0; + sp.request = REQ_STD_SET_ADDRESS; + sp.value = dev->addr; + sp.index = 0; + sp.length = 0; + err = + h->dev->tx_pkt (cfg, USB_TOKEN_SETUP, &sp, 0, sizeof (sp), NULL, true); + if (err != USB_HOST_ERR_NONE) + { + PANIC ("USB: WHOOPS!!!!!!!\n"); + } + err = h->dev->tx_pkt (cfg, USB_TOKEN_IN, NULL, 0, 0, NULL, true); + if (err != USB_HOST_ERR_NONE) + { + PANIC ("USB: Error on setting device address (err = %d)\n", err); + } + + h->dev->modify_dev_channel (dev->h_dev, dev->addr, dev->usb_version); +} + +#define MAX_USB_STR 128 +/* read string by string descriptor index from usb device */ +static char * +usb_get_string (struct usb_dev *udev, int ndx) +{ + struct usb_setup_pkt sp; + char str[MAX_USB_STR]; + char *ret; + int sz; + + sp.recipient = USB_SETUP_RECIP_DEV; + sp.type = USB_SETUP_TYPE_STD; + sp.direction = 1; + sp.request = REQ_STD_GET_DESC; + sp.value = (SETUP_DESC_STRING << 8) | ndx; + sp.index = 0; + sp.length = MAX_USB_STR; + udev->host->dev->tx_pkt (udev->h_cfg_eop, USB_TOKEN_SETUP, + &sp, 0, sizeof (sp), NULL, false); + sz = usb_tx_all (&udev->cfg_eop, &str, MAX_USB_STR, 2, true); + sz += + usb_tx_all (&udev->cfg_eop, str + sz, (uint8_t) (str[0]) - sz, 0, true); + + /* string failed to tx? */ + if (sz == 0) + return NULL; + + send_status (udev->host, udev->h_cfg_eop); + + /* some devices don't respect the string descriptor length value (str[0]) + * and just send any old value they want, so we can't use it */ + + str[(sz < (MAX_USB_STR - 1)) ? (sz) : (MAX_USB_STR - 1)] = '\0'; + + /* usb uses wchars for strings, convert to ASCII */ + wchar_to_ascii (str, str + 2); + ret = malloc (strlen (str) + 1); + strlcpy (ret, str, MAX_USB_STR); + return ret; +} + +static struct class * +usb_get_class_by_id (int id) +{ + struct list_elem *li; + + li = list_begin (&class_list); + while (li != list_end (&class_list)) + { + struct class *c; + c = list_entry (li, struct class, peers); + if (c->dev->class_id == id) + return c; + li = list_next (li); + } + + return NULL; +} + +/** + * Asssociate interfaces on a device with their respective device classes + */ +static void +usb_attach_interfaces (struct usb_dev *dev) +{ + + struct list_elem *li; + li = list_begin (&dev->interfaces); + while (li != list_end (&dev->interfaces)) + { + struct class *cl; + struct usb_iface *ui; + + ui = list_entry (li, struct usb_iface, peers); + li = list_next (li); + cl = usb_get_class_by_id (ui->class_id); + + /* no matching class? try next interface */ + if (cl == NULL) + continue; + + ui->c_info = cl->dev->attached (ui); + /* did class driver initialize it OK? */ + if (ui->c_info != NULL) + { + /* yes, add to list of usable interfaces */ + list_push_back (&cl->usb_ifaces, &ui->class_peers); + ui->class = cl; + } + } +} + +/** + * Scan all interfaces for interfaces that match class's class id + * Attach interfaces that match + */ +static void +usb_apply_class_to_interfaces (struct class *c) +{ + struct list_elem *li; + + li = list_begin (&usb_dev_list); + while (li != list_end (&usb_dev_list)) + { + struct usb_dev *ud; + struct list_elem *lii; + + ud = list_entry (li, struct usb_dev, sys_peers); + + lii = list_begin (&ud->interfaces); + while (lii != list_end (&ud->interfaces)) + { + struct usb_iface *ui; + + ui = list_entry (lii, struct usb_iface, peers); + + lii = list_next (lii); + if (ui->class_id != c->dev->class_id) + continue; + + ui->c_info = c->dev->attached (ui); + if (ui->c_info == NULL) + continue; + + list_push_back (&c->usb_ifaces, &ui->class_peers); + ui->class = c; + } + + li = list_next (li); + } +} + +int +usb_dev_bulk (struct usb_endpoint *eop, void *buf, int sz, int *tx) +{ + struct host *h; + int err; + int token; + + ASSERT (eop != NULL); + h = eop->iface->dev->host; + + if (eop->direction == 0) + token = USB_TOKEN_OUT; + else + token = USB_TOKEN_IN; + + err = h->dev->tx_pkt (eop->h_eop, token, buf, sz, sz, tx, true); + + return err; +} + +int +usb_dev_setup (struct usb_endpoint *eop, bool in, + struct usb_setup_pkt *s, void *buf, int sz) +{ + struct host *h; + int err; + + ASSERT (eop != NULL); + h = eop->iface->dev->host; + + err = h->dev->tx_pkt (eop->h_eop, USB_TOKEN_SETUP, s, + 0, sizeof (struct usb_setup_pkt), NULL, true); + if (err != USB_HOST_ERR_NONE) + { + printf ("usb_dev_setup: failed\n"); + return 0; + } + + err = h->dev->tx_pkt (eop->h_eop, (in) ? USB_TOKEN_IN : USB_TOKEN_OUT, + buf, 0, sz, &sz, true); + + return sz; +} + +/** convert a wchar string to ascii in place */ +static size_t +wchar_to_ascii (char *dst, const char *src) +{ + size_t sz = 0; + for (sz = 0; src[sz * 2] != '\0'; sz++) + { + dst[sz] = src[sz * 2]; + } + dst[sz] = '\0'; + return sz; +} + +/* this is used for variable sized transfers where a normal bulk transfer would + probably fail, since it expects some minimum size - we just want to + read/write as much to the pipe as we can + */ +static int +usb_tx_all (struct usb_endpoint *eop, void *buf, + int max_bytes, int bailout, bool in) +{ + int txed; + int token; + int prev_sz = 0; + struct host *h; + + if (max_bytes <= 0) + return 0; + + if (bailout == 0) + bailout = 512; + + txed = 0; + token = (in) ? USB_TOKEN_IN : USB_TOKEN_OUT; + h = eop->iface->dev->host; + while (txed < max_bytes && txed < bailout) + { + int sz, err; + sz = 0; + err = h->dev->tx_pkt (eop->h_eop, token, + buf + txed, 0, max_bytes - txed, &sz, true); + if (prev_sz == 0) + prev_sz = sz; + txed += sz; + /* this should probably be using short packet detection */ + if (err != USB_HOST_ERR_NONE || sz != prev_sz || sz == 0) + { + return txed; + } + } + return txed; +} diff --git a/src/devices/usb.h b/src/devices/usb.h new file mode 100644 index 0000000..4ecce3b --- /dev/null +++ b/src/devices/usb.h @@ -0,0 +1,199 @@ +#ifndef USB_H +#define USB_H + +#include +#include +#include + +#define USB_HOST_ERR_NONE 0 +#define USB_HOST_ERR_BITSTUFF 1 +#define USB_HOST_ERR_TIMEOUT 2 +#define USB_HOST_ERR_NAK 3 +#define USB_HOST_ERR_BABBLE 4 +#define USB_HOST_ERR_BUFFER 5 +#define USB_HOST_ERR_STALL 6 +#define USB_HOST_ERR_NODEV 7 + +#define make_usb_pid(x) ((x) | ((~(x)) << 4)) + +/* token packets... */ +#define USB_PID_OUT make_usb_pid(1) +#define USB_PID_IN make_usb_pid(9) +#define USB_PID_SOF make_usb_pid(5) +#define USB_PID_SETUP make_usb_pid(13) +/* data packets... */ +#define USB_PID_DATA0 make_usb_pid(3) +#define USB_PID_DATA1 make_usb_pid(11) +#define USB_PID_DATA2 make_usb_pid(7) +#define USB_PID_MDATA make_usb_pid(15) +/* handshake packets.. */ +#define USB_PID_ACK make_usb_pid(2) +#define USB_PID_NAK make_usb_pid(10) +#define USB_PID_STALL make_usb_pid(14) +#define USB_PID_NYET make_usb_pid(6) +/* special */ +#define USB_PID_PRE make_usb_pid(12) +#define USB_PID_ERR make_usb_pid(12) +#define USB_PID_SPLIT make_usb_pid(8) +#define USB_PID_PING make_usb_pid(4) + +/* the standard setup requests */ +#define REQ_STD_GET_STATUS 0 +#define REQ_STD_CLR_FEAT 1 +#define REQ_STD_SET_FEAT 3 +#define REQ_STD_SET_ADDRESS 5 +#define REQ_STD_GET_DESC 6 +#define REQ_STD_SET_DESC 7 +#define REQ_STD_GET_CONFIG 8 +#define REQ_STD_SET_CONFIG 9 +#define REQ_STD_GET_IFACE 10 +#define REQ_STD_SET_IFACE 11 +#define REQ_STD_SYNCH_FRAME 12 + + +#define USB_TOKEN_SETUP 0x00 +#define USB_TOKEN_IN 0x80 +#define USB_TOKEN_OUT 0x90 + +struct class; +struct host; +typedef void *host_info; +typedef void *host_eop_info; +typedef void *host_dev_info; +typedef void *class_info; + + +struct usb_iface +{ + int iface_num; + int class_id, subclass_id; + int proto; + + struct usb_dev *dev; + + struct class *class; + class_info c_info; + + struct list_elem class_peers; /* peers on class */ + struct list endpoints; + + struct list_elem peers; /* peers on device */ +}; + +#define USB_SPEED_1 0 +#define USB_SPEED_1_1 1 +#define USB_SPEED_2 2 + +struct usb_host +{ + const char *name; + int (*detect_change) (host_info); + int (*tx_pkt) (host_eop_info, int pid, void *pkt, + int min_sz, int max_sz, int *in_sz, bool wait); + + host_eop_info (*create_eop)(host_dev_info, int eop, int maxpkt); + void (*remove_eop)(host_eop_info); + + host_dev_info (*create_dev_channel) (host_info, int dev_addr, int ver); + void (*modify_dev_channel) (host_dev_info, int dev_addr, int ver); + void (*remove_dev_channel) (host_dev_info); + + void (*set_toggle) (host_eop_info, int toggle); +}; + +struct usb_dev; +struct usb_class +{ + const int class_id; + const char *name; + + /* when a device of this class is attached, the device is passed in */ + /* returns private info on device */ + void *(*attached) (struct usb_iface *); + + /* device is detached -> detached(dev_info) */ + void (*detached) (class_info info); +}; + +#define USB_VERSION_1_0 0x100 +#define USB_VERSION_1_1 0x110 +#define USB_VERSION_2 0x200 + +#define USB_EOP_ATTR_CTL 0 /* control */ +#define USB_EOP_ATTR_ISO 1 /* isochronous */ +#define USB_EOP_ATTR_BULK 2 /* bulk */ +#define USB_EOP_ATTR_INT 3 /* interrupt */ +struct usb_endpoint +{ + int eop; /* end point address */ + int attr; + int direction; /* 0 = host->dev, 1=dev->host */ + int max_pkt; + int interval; + struct usb_iface *iface; + host_eop_info h_eop; + struct list_elem peers; +}; + +struct usb_dev +{ + uint8_t addr; + int usb_version; + int class_id, subclass_id; + uint16_t product_id, vendor_id, device_id; + int interface; + int max_pkt_len; + int int_period; + char *manufacturer; + char *product; + int pwr; /* power draw for this config */ + bool ignore_device; + struct list interfaces; + + struct list_elem host_peers; /* peers on host */ + struct list_elem sys_peers; /* list for all devices */ + + struct usb_iface default_iface; + struct usb_endpoint cfg_eop; + + host_dev_info h_dev; + host_eop_info h_cfg_eop; /* configuration EOP */ + struct host *host; +}; + +/* pg276 usb_20.pdf */ +#define USB_SETUP_TYPE_STD 0 +#define USB_SETUP_TYPE_CLASS 1 +#define USB_SETUP_TYPE_VENDOR 2 + +#define USB_SETUP_RECIP_DEV 0 +#define USB_SETUP_RECIP_IFACE 1 +#define USB_SETUP_RECIP_ENDPT 2 +#define USB_SETUP_RECIP_OTHER 3 + +#pragma pack(1) +struct usb_setup_pkt +{ + uint8_t recipient:5; + uint8_t type:2; + uint8_t direction:1; /* 0 = host->dev, 1 = dev->host */ + uint8_t request; + uint16_t value; + uint16_t index; + uint16_t length; +}; +#pragma pack() + +void usb_init (void); + +void usb_register_host (struct usb_host *, host_info info); +int usb_unregister_host (struct usb_host *, host_info info); +int usb_register_class (struct usb_class *); +int usb_unregister_class (struct usb_class *); + +int usb_dev_bulk (struct usb_endpoint *eop, void *buf, int sz, int *tx); +int usb_dev_setup (struct usb_endpoint *eop, bool in, + struct usb_setup_pkt *s, void *buf, int sz); +int usb_dev_wait_int (struct usb_dev *); + +#endif diff --git a/src/devices/usb_ehci.c b/src/devices/usb_ehci.c new file mode 100644 index 0000000..34f328b --- /dev/null +++ b/src/devices/usb_ehci.c @@ -0,0 +1,52 @@ +/** + * + * Actual EHCI will be implemented later + * + * For now, we just deactivate the EHCI controller and routing circuitry + * so that any USB2.0 devices activated by the BIOS will show up on the + * USB1.1 controllers instead of being routed to EHCI and therefore "invisible" + * to the system. + * + */ + +#include "devices/pci.h" +#include "devices/usb.h" +#include +#include + +/* capability registers */ +#define EHCI_REG_CAPLENGTH 0x00 + +/* operational regisers - must be offset by op_base */ +#define EHCI_REG_CONFIGFLAG 0x40 + +void ehci_init (void); + + +void +ehci_init (void) +{ + struct pci_dev *pd; + int dev_num; + + dev_num = 0; + while ((pd = pci_get_dev_by_class (PCI_MAJOR_SERIALBUS, PCI_MINOR_USB, + PCI_USB_IFACE_EHCI, dev_num)) != NULL) + { + struct pci_io *io; + uint8_t op_base; + + dev_num++; + io = pci_io_enum (pd, NULL); + if (io == NULL) + { + printf ("IO not found on EHCI device?\n"); + continue; + } + printf ("Disabling the EHCI controller #%d\n", dev_num - 1); + op_base = pci_reg_read8 (io, EHCI_REG_CAPLENGTH); + + /* turn off EHCI routing */ + pci_reg_write32 (io, EHCI_REG_CONFIGFLAG + op_base, 0); + } +} diff --git a/src/devices/usb_hub.c b/src/devices/usb_hub.c new file mode 100644 index 0000000..01bbfb9 --- /dev/null +++ b/src/devices/usb_hub.c @@ -0,0 +1,54 @@ +#include "devices/usb.h" +#include "devices/usb_hub.h" +#include + +#define USB_CLASS_HUB 0x09 + +#define REQ_HUB_GET_STATUS 0 +#define REQ_HUB_CLEAR_FEATURE 1 +#define REQ_HUB_SET_FEATURE 2 +#define REQ_HUB_GET_DESC 6 +#define REQ_HUB_SET_DESC 7 +#define REQ_HUB_CLEAR_TT_BUF 8 +#define REQ_HUB_RESET_TT 9 +#define REQ_HUB_GET_TT_STATE 10 +#define REQ_HUB_STOP_TT 11 + +#define HUB_SEL_HUB_POWER 0 +#define HUB_SEL_OVER_CURRENT 1 +#define HUB_SEL_PORT_CONN 0 +#define HUB_SEL_PORT_ENABLE 1 +#define HUB_SEL_PORT_SUSPEND 2 +#define HUB_SEL_PORT_OVER_CURRENT 3 +#define HUB_SEL_PORT_RESET 4 +#define HUB_SEL_PORT_POWER 8 +#define HUB_SEL_PORT_LOW_SPEED 9 + +#define SETUP_DESC_HUB 0x29 + +static void* hub_attached(struct usb_iface*); +static void hub_detached(class_info); + +static struct usb_class hub_class = { + .attached = hub_attached, + .detached = hub_detached, + .class_id = USB_CLASS_HUB, + .name = "hub" + }; + + +void usb_hub_init(void) +{ + usb_register_class(&hub_class); +} + + +static void* hub_attached(struct usb_iface* ui UNUSED) +{ + return NULL; +} + +static void hub_detached(class_info info UNUSED) +{ +} + diff --git a/src/devices/usb_hub.h b/src/devices/usb_hub.h new file mode 100644 index 0000000..d4399d7 --- /dev/null +++ b/src/devices/usb_hub.h @@ -0,0 +1,7 @@ +#ifndef HUB_H +#define HUB_H + +void usb_hub_init(void); + + +#endif diff --git a/src/devices/usb_storage.c b/src/devices/usb_storage.c new file mode 100644 index 0000000..b34121a --- /dev/null +++ b/src/devices/usb_storage.c @@ -0,0 +1,440 @@ +/** + * USB mass storage driver - just like the one Elvis used! + */ + +#include +#include +#include +#include +#include +#include +#include "threads/malloc.h" +#include "threads/palloc.h" +#include "threads/thread.h" +#include "devices/timer.h" +#include "threads/synch.h" +#include "threads/pte.h" +#include "devices/usb.h" +#include "devices/block.h" +#include "devices/partition.h" + +//http://www.usb.org/developers/defined_class +#define USB_CLASS_MASS_STORAGE 0x08 + +#define USB_SUBCLASS_RBC 0x01 /* reduced block commands */ +#define USB_SUBCLASS_ATAPI 0x02 /* ATAPI - CD's */ +#define USB_SUBCLASS_TAPE 0x03 +#define USB_SUBCLASS_UFI 0x04 /* floppy disk */ +#define USB_SUBCLASS_SFF8070 0x05 +#define USB_SUBCLASS_SCSI 0x06 /* scsi transparent command set */ + +#define USB_PROTO_COMPLETE 0x00 +#define USB_PROTO_NO_COMPLETE 0x01 +#define USB_PROTO_BULK 0x50 + +#pragma pack(1) + +#define CBW_SIG_MAGIC 0x43425355 + +#define CBW_FL_IN (1 << 7) +#define CBW_FL_OUT 0 + +/** usbmassbulk_10.pdf - pg 13 */ +/* command block wrapper */ +struct msc_cbw +{ + uint32_t sig; + uint32_t tag; + uint32_t tx_len; + uint8_t flags; + uint8_t lun; + uint8_t cb_len; /* command block length */ + uint8_t cb[16]; /* command block */ +}; + +#define CSW_SIG_MAGIC 0x53425355 +#define CSW_STATUS_PASSED 0 +#define CSW_STATUS_FAILED 1 +#define CSW_STATUS_PHASE 2 /* phase error */ +/* command status wrapper */ +struct msc_csw +{ + uint32_t sig; + uint32_t tag; + uint32_t residue; + uint8_t status; +}; + +struct scsi_cdb6 +{ + uint8_t op; + uint8_t lba[3]; + uint8_t len; + uint8_t control; +}; + +struct scsi_cdb10 +{ + uint8_t op; + uint8_t action; + uint32_t lba; + uint8_t resv; + uint16_t len; + uint8_t control; +}; + +struct scsi_cdb12 +{ + uint8_t op; + uint8_t action; + uint32_t lba; + uint32_t len; + uint8_t resv; + uint8_t control; +}; + +struct scsi_cdb16 +{ + uint8_t op; + uint8_t action; + uint32_t lba; + uint32_t data; + uint32_t len; + uint8_t resv; + uint8_t control; +}; + +struct scsi_capacity10 +{ + uint32_t blocks; + uint32_t block_len; +}; + +#define SCSI_OP_WRITE6 0xa +#define SCSI_OP_READ6 0x8 +#define SCSI_OP_TEST_READY 0x00 +#define SCSI_OP_SEEK8 0x0b +#define SCSI_OP_MODE_SENSE8 0x1a +#define SCSI_OP_MODE_SELECT8 0x15 +#define SCSI_OP_READ_CAPACITY10 0x25 +#define SCSI_OP_READ_CAPACITY16 0x9e +#define SCSI_OP_WRITE10 0x2a +#define SCSI_OP_READ10 0x28 +#define SCSI_OP_SEEK10 0x2b +#define SCSI_OP_MODE_SENSE10 0x5a +#define SCSI_OP_MODE_SELECT10 0x55 + + +#pragma pack() + +static void *msc_attached (struct usb_iface *); +static void msc_detached (class_info); + +static void msc_reset_endpoint (struct usb_endpoint *eop); + +static struct usb_class storage_class = { + .attached = msc_attached, + .detached = msc_detached, + .name = "Mass Storage", + .class_id = USB_CLASS_MASS_STORAGE +}; + +#define mci_lock(x) lock_acquire(&(x)->lock) +#define mci_unlock(x) lock_release(&(x)->lock) + +struct msc_class_info +{ + int refs; + struct lock lock; + struct usb_iface *ui; + int blk_size; + int blk_count; + bool retrying; + uint32_t tag; + void *bounce_buffer; + + struct usb_endpoint *eop_in; + struct usb_endpoint *eop_out; + struct list_elem peers; +}; +static void msc_get_geometry (struct msc_class_info *); +static void msc_io (struct msc_class_info *, block_sector_t, void *buf, bool wr); +static void msc_reset_recovery(struct msc_class_info* mci); +static void msc_bulk_reset(struct msc_class_info* mci); + +struct msc_blk_info +{ + struct msc_class_info *mci; +}; + +static struct list device_list; +static struct block_operations msc_operations; + +void usb_storage_init (void); + +void +usb_storage_init (void) +{ + list_init (&device_list); + usb_register_class (&storage_class); +} + + +static class_info +msc_attached (struct usb_iface *ui) +{ + static int dev_no; + + struct msc_class_info *mci; + struct msc_blk_info *mbi; + struct list_elem *li; + struct block *block; + char name[16]; + + if (ui->subclass_id != USB_SUBCLASS_SCSI) + { + printf ("usb_storage: Only support SCSI-type devices\n"); + return NULL; + } + + mci = malloc (sizeof (struct msc_class_info)); + + mci->refs = 0; + mci->retrying = false; + lock_init (&mci->lock); + mci->ui = ui; + list_push_back (&device_list, &mci->peers); + + /* grab endpoints */ + li = list_begin (&ui->endpoints); + mci->eop_in = NULL; + mci->eop_out = NULL; + while (li != list_end (&ui->endpoints)) + { + struct usb_endpoint *ue; + + ue = list_entry (li, struct usb_endpoint, peers); + li = list_next (li); + + if (ue->attr == USB_EOP_ATTR_BULK) + { + if (ue->direction == 0) + mci->eop_out = ue; + else + mci->eop_in = ue; + } + } + + ASSERT (mci->eop_in != NULL && mci->eop_out != NULL); + + msc_get_geometry (mci); + + if (mci->blk_size != 512) + { + printf ("ignoring device with %d-byte sectors\n", mci->blk_size); + return NULL; + } + + mci->bounce_buffer = palloc_get_multiple (PAL_ASSERT, + DIV_ROUND_UP (mci->blk_size, + PGSIZE)); + mbi = malloc (sizeof (struct msc_blk_info)); + mbi->mci = mci; + snprintf (name, sizeof name, "ud%c", 'a' + dev_no++); + block = block_register (name, BLOCK_RAW, "USB", mci->blk_count, + &msc_operations, mbi); + partition_scan (block); + + return mci; +} + +static void +msc_detached (class_info ci UNUSED) +{ + PANIC ("msc_detached: STUB"); +} + +static void +msc_read (void *mbi_, block_sector_t sector, void *buffer) +{ + struct msc_blk_info *mbi = mbi_; + + mci_lock (mbi->mci); + msc_io (mbi->mci, sector, buffer, false); + mci_unlock (mbi->mci); +} + +static void +msc_write (void *mbi_, block_sector_t sector, const void *buffer) +{ + struct msc_blk_info *mbi = mbi_; + + mci_lock (mbi->mci); + msc_io (mbi->mci, sector, (void *) buffer, true); + mci_unlock (mbi->mci); +} + +static struct block_operations msc_operations = + { + msc_read, + msc_write, + }; + +static void +msc_get_geometry (struct msc_class_info *mci) +{ + struct msc_cbw cbw; + struct scsi_capacity10 *cap; + uint8_t buf[sizeof (struct msc_csw) + sizeof (struct scsi_capacity10)]; + struct scsi_cdb10 *cdb; + struct msc_csw *csw; + int tx; + + /* cap + csw must be read in one shot, combine into a single buffer */ + cap = (struct scsi_capacity10 *) (buf); + csw = (struct msc_csw *) (&buf[sizeof (struct scsi_capacity10)]); + + cbw.sig = CBW_SIG_MAGIC; + cbw.tag = mci->tag++; + cbw.tx_len = sizeof (struct scsi_capacity10); + cbw.flags = CBW_FL_IN; + cbw.lun = 0; + cbw.cb_len = sizeof (struct scsi_cdb10); + + cdb = (void *) (&cbw.cb); + memset (cdb, 0, sizeof (struct scsi_cdb10)); + cdb->op = SCSI_OP_READ_CAPACITY10; + + usb_dev_bulk (mci->eop_out, &cbw, sizeof (cbw), &tx); + usb_dev_bulk (mci->eop_in, &buf, sizeof (buf), &tx); + + mci->blk_count = be32_to_machine (cap->blocks) + 1; + mci->blk_size = be32_to_machine (cap->block_len); + + /* did CSW stall? */ + if (tx == sizeof (struct scsi_capacity10)) + { + msc_reset_endpoint (mci->eop_in); + usb_dev_bulk (mci->eop_in, csw, sizeof (*csw), &tx); + } + + ASSERT (csw->sig == CSW_SIG_MAGIC); + + if (csw->status != CSW_STATUS_PASSED) + { + PANIC ("USB storage geometry read failure!\n"); + } + +} + +static void +msc_io (struct msc_class_info *mci, block_sector_t bn, void *buf, bool wr) +{ + struct msc_cbw cbw; + struct msc_csw csw; + struct scsi_cdb10 *cdb; + int tx; + int err; + + if (wr) + memcpy (mci->bounce_buffer, buf, mci->blk_size); + + memset (&cbw, 0, sizeof (cbw)); + cbw.sig = CBW_SIG_MAGIC; + cbw.tag = mci->tag++; + cbw.tx_len = mci->blk_size; + cbw.flags = (wr) ? CBW_FL_OUT : CBW_FL_IN; + cbw.lun = 0; + cbw.cb_len = sizeof (struct scsi_cdb10); + + cdb = (void *) (&cbw.cb); + cdb->op = (wr) ? SCSI_OP_WRITE10 : SCSI_OP_READ10; + *((uint32_t *) ((uint8_t *) (&cdb->lba) + 1)) = machine_to_be24 (bn); + cdb->len = machine_to_be16 (1); + +// msc_reset_endpoint (mci->eop_in); +// msc_reset_endpoint (mci->eop_out); + + /* set it up */ + err = usb_dev_bulk (mci->eop_out, &cbw, sizeof (cbw), &tx); + if (err != 0) + { + msc_reset_endpoint (mci->eop_out); + err = usb_dev_bulk (mci->eop_out, &cbw, sizeof (cbw), &tx); + } + + /* do storage io */ + err = usb_dev_bulk ((wr) ? mci->eop_out : mci->eop_in, + mci->bounce_buffer, mci->blk_size, &tx); + memset (&csw, 0, sizeof (csw)); + ASSERT (tx == mci->blk_size); + + + /* get command status */ + err = usb_dev_bulk (mci->eop_in, &csw, sizeof (csw), &tx); + if (err != 0) + { + msc_reset_endpoint (mci->eop_in); + msc_reset_endpoint (mci->eop_out); + err = usb_dev_bulk (mci->eop_in, &csw, sizeof (csw), &tx); + if (err != 0) + PANIC ("msc_io: error %d\n", err); + } + + if (csw.sig != CSW_SIG_MAGIC) + { + if (mci->retrying == true) + PANIC ("usb_msd: CSW still missing. Bail out\n"); + printf ("usb_msd: no command status, resetting. Buggy device?\n"); + msc_reset_recovery(mci); + printf ("reset complete\n"); + mci->retrying = true; + msc_io (mci, bn, buf, wr); + return; + } + mci->retrying = false; + + if (csw.status != CSW_STATUS_PASSED) + { + PANIC ("USB storage IO failure! - error %d\n", csw.status); + } + if (!wr) + memcpy (buf, mci->bounce_buffer, mci->blk_size); +} + +static void +msc_reset_endpoint (struct usb_endpoint *eop) +{ + struct usb_setup_pkt sp; + + sp.recipient = USB_SETUP_RECIP_ENDPT; + sp.type = USB_SETUP_TYPE_STD; + sp.direction = 0; + sp.request = REQ_STD_CLR_FEAT; + sp.value = 0; /* 0 is ENDPOINT_HALT */ + sp.index = eop->eop; + sp.length = 0; + usb_dev_setup (eop, true, &sp, NULL, 0); +} + +static void +msc_reset_recovery(struct msc_class_info* mci) +{ + msc_bulk_reset (mci); + msc_reset_endpoint (mci->eop_in); + msc_reset_endpoint (mci->eop_out); +} + +static void msc_bulk_reset(struct msc_class_info* mci) +{ + struct usb_setup_pkt sp; + + sp.recipient = USB_SETUP_RECIP_DEV; + sp.type = USB_SETUP_TYPE_CLASS; + sp.direction = 0; + sp.request = 0xff; + sp.value = 0; + sp.index = mci->ui->iface_num; + sp.length = 0; + usb_dev_setup (&mci->ui->dev->cfg_eop, true, &sp, NULL, 0); +} diff --git a/src/devices/usb_uhci.c b/src/devices/usb_uhci.c new file mode 100644 index 0000000..77b8333 --- /dev/null +++ b/src/devices/usb_uhci.c @@ -0,0 +1,1305 @@ +/** + * Universal Host Controller Interface driver + * TODO: + * Stall timeouts + * Better (any) root hub handling + */ + +#include +#include +#include +#include +#include "threads/pte.h" +#include "threads/malloc.h" +#include "threads/palloc.h" +#include "threads/synch.h" +#include "threads/interrupt.h" +#include "threads/thread.h" +#include "devices/pci.h" +#include "devices/usb.h" +#include "devices/timer.h" + +#define UHCI_MAX_PORTS 8 + +#define FRAME_LIST_ENTRIES 1024 +#define TD_ENTRIES (4096/32) /* number of entries allocated */ +#define QH_ENTRIES (4096/16) + +/* uhci pci registers */ +#define UHCI_REG_USBCMD 0x00 /* Command */ +#define UHCI_REG_USBSTS 0x02 /* Status */ +#define UHCI_REG_USBINTR 0x04 /* interrupt enable */ +#define UHCI_REG_FRNUM 0x06 /* frame number */ +#define UHCI_REG_FLBASEADD 0x08 /* frame list base address */ +#define UHCI_REG_SOFMOD 0x0c /* start of frame modify */ +#define UHCI_REG_PORTSC1 0x10 /* port 1 status/control */ +#define UHCI_REG_PORTSC2 0x12 /* port 2 status/control */ +#define UHCI_REGSZ 0x20 /* register iospace size */ + +/* in PCI config space for some reason */ +#define UHCI_REG_LEGSUP 0xC0 + + +/* command register */ +#define USB_CMD_MAX_PACKET (1 << 7) +#define USB_CMD_CONFIGURE (1 << 6) +#define USB_CMD_FGR (1 << 4) /* force global resume */ +#define USB_CMD_EGSM (1 << 3) /* global suspend mode */ +#define USB_CMD_GRESET (1 << 2) /* global reset */ +#define USB_CMD_HCRESET (1 << 1) /* host controller reset */ +#define USB_CMD_RS (1 << 0) /* run/stop */ + +/* status register */ +#define USB_STATUS_HALTED (1 << 5) +#define USB_STATUS_PROCESSERR (1 << 4) +#define USB_STATUS_HOSTERR (1 << 3) +#define USB_STATUS_RESUME (1 << 2) +#define USB_STATUS_INTERR (1 << 1) +#define USB_STATUS_USBINT (1 << 0) + +/* interrupt enable register */ +#define USB_INTR_SHORT (1 << 3) /* enable short packets */ +#define USB_INTR_IOC (1 << 2) /* interrupt on complete */ +#define USB_INTR_RESUME (1 << 1) /* resume interrupt enable */ +#define USB_INTR_TIMEOUT (1 << 0) /* timeout int enable */ + +/* port control register */ +#define USB_PORT_SUSPEND (1 << 12) +#define USB_PORT_RESET (1 << 9) +#define USB_PORT_LOWSPEED (1 << 8) +#define USB_PORT_RESUMED (1 << 6) /* resume detected */ +#define USB_PORT_CHANGE (1 << 3) /* enable change */ +#define USB_PORT_ENABLE (1 << 2) /* enable the port */ +#define USB_PORT_CONNECTCHG (1 << 1) /* connect status changed */ +#define USB_PORT_CONNECTSTATUS (1 << 0) /* device is connected */ + +#define ptr_to_flp(x) (((uintptr_t)x) >> 4) +#define flp_to_ptr(x) (uintptr_t)(((uintptr_t)x) << 4) + +/* frame structures */ +#pragma pack(1) +struct frame_list_ptr +{ + uint32_t terminate:1; + uint32_t qh_select:1; + uint32_t depth_select:1; /* only for TD */ + uint32_t resv:1; /* zero */ + uint32_t flp:28; /* frame list pointer */ +}; + +struct td_token +{ + uint32_t pid:8; /* packet id */ + uint32_t dev_addr:7; /* device address */ + uint32_t end_point:4; + uint32_t data_toggle:1; + uint32_t resv:1; + uint32_t maxlen:11; /* maximum pkt length */ +}; + +struct td_control +{ + uint32_t actual_len:11; + uint32_t resv1:5; + + /* status information */ + uint32_t resv2:1; + uint32_t bitstuff:1; + uint32_t timeout:1; + uint32_t nak:1; + uint32_t babble:1; + uint32_t buffer_error:1; + uint32_t stalled:1; + uint32_t active:1; + + /* config data */ + uint32_t ioc:1; /* issue int on complete */ + uint32_t ios:1; /* isochronous select */ + uint32_t ls:1; /* low speed device */ + uint32_t error_limit:2; /* errors before interrupt */ + uint32_t spd:1; /* short packet detect */ + uint32_t resv3:2; +}; + +#define TD_FL_ASYNC 1 +#define TD_FL_USED 0x80000000 + +struct tx_descriptor +{ + struct frame_list_ptr flp; + struct td_control control; + struct td_token token; + uint32_t buf_ptr; + + + struct frame_list_ptr *head; /* for fast removal */ + uint32_t flags; +}; + +struct queue_head +{ + struct frame_list_ptr qhlp; /* queue head link pointer */ + struct frame_list_ptr qelp; /* queue elem link pointer */ +}; +#pragma pack() + +struct uhci_info +{ + struct pci_dev *dev; + struct pci_io *io; /* pci io space */ + struct lock lock; + struct frame_list_ptr *frame_list; /* page aligned frame list */ + + struct tx_descriptor *td_pool; + struct bitmap *td_used; + struct queue_head *qh_pool; + struct bitmap *qh_used; + + uint8_t num_ports; + uint8_t attached_ports; + + int timeouts; /* number of timeouts */ + + struct semaphore td_sem; + struct list devices; /* devices on host */ + struct list waiting; /* threads waiting */ +}; + +struct usb_wait +{ + struct tx_descriptor *td; + struct uhci_dev_info *ud; + struct semaphore sem; + struct list_elem peers; +}; + +struct uhci_dev_info +{ + struct uhci_info *ui; /* owner */ + bool low_speed; /* whether device is low speed */ + int dev_addr; /* device address */ + int errors; /* aggregate errors */ + struct list_elem peers; /* next dev on host */ + struct lock lock; + struct queue_head *qh; +}; + +struct uhci_eop_info +{ + struct uhci_dev_info *ud; + int eop; + int maxpkt; /* max packet size */ + int toggle; /* data toggle bit for bulk transfers */ +}; + +#define uhci_lock(x) lock_acquire(&(x)->lock) +#define uhci_unlock(x) lock_release(&(x)->lock) +#define dev_lock(x) lock_acquire(&(x)->lock) +#define dev_unlock(x) lock_release(&(x)->lock) + + +static int token_to_pid (int token); + +static int uhci_tx_pkt (host_eop_info eop, int token, void *pkt, + int min_sz, int max_sz, int *in_sz, bool wait); + +static int uhci_detect_change (host_info); + + +static int uhci_tx_pkt_now (struct uhci_eop_info *ue, int token, void *pkt, + int sz); +static int uhci_tx_pkt_wait (struct uhci_eop_info *ue, int token, void *pkt, + int max_sz, int *in_sz); +static int uhci_tx_pkt_bulk (struct uhci_eop_info *ue, int token, void *buf, + int sz, int *tx); + + +static int uhci_process_completed (struct uhci_info *ui); + +static struct tx_descriptor *uhci_acquire_td (struct uhci_info *); +static void uhci_release_td (struct uhci_info *, struct tx_descriptor *); +static void uhci_remove_qh (struct uhci_info *ui, struct queue_head *qh); + + +static void qh_free (struct uhci_info *ui, struct queue_head *qh); +static struct queue_head *qh_alloc (struct uhci_info *ui); + +static struct uhci_info *uhci_create_info (struct pci_io *io); +static void uhci_destroy_info (struct uhci_info *ui); +static host_eop_info uhci_create_eop (host_dev_info hd, int eop, int maxpkt); +static void uhci_remove_eop (host_eop_info hei); +static host_dev_info uhci_create_chan (host_info hi, int dev_addr, int ver); +static void uhci_destroy_chan (host_dev_info); +static void uhci_modify_chan (host_dev_info, int dev_addr, int ver); +static void uhci_set_toggle (host_eop_info, int toggle); + +static struct tx_descriptor *td_from_pool (struct uhci_info *ui, int idx); + +static int check_and_flip_change (struct uhci_info *ui, int reg); +static void uhci_stop (struct uhci_info *ui); +static void uhci_run (struct uhci_info *ui); +static void uhci_stop_unlocked (struct uhci_info *ui); +static void uhci_run_unlocked (struct uhci_info *ui); +#define uhci_is_stopped(x) (pci_reg_read16((x)->io, UHCI_REG_USBSTS) \ + & USB_STATUS_HALTED) +#define uhci_port_enabled(x, y) (pci_reg_read16((x)->io, (y)) & USB_PORT_ENABLE) +static void uhci_add_td_to_qh (struct queue_head *qh, + struct tx_descriptor *td); +static void uhci_remove_error_td (struct tx_descriptor *td); +static void uhci_setup_td (struct tx_descriptor *td, int dev_addr, int token, + int eop, void *pkt, int sz, int toggle, bool ls); +static int uhci_enable_port (struct uhci_info *ui, int port); +static void uhci_irq (void *uhci_data); +static void uhci_detect_ports (struct uhci_info *ui); + +static int uhci_remove_stalled (struct uhci_info *ui); +static void uhci_stall_watchdog (struct uhci_info *ui); + +static void dump_all_qh (struct uhci_info *ui); +static void dump_qh (struct queue_head *qh); + +static void dump_regs (struct uhci_info *ui); + +void uhci_init (void); + + +static struct usb_host uhci_host = { + .name = "UHCI", + .tx_pkt = uhci_tx_pkt, + .detect_change = uhci_detect_change, + .create_dev_channel = uhci_create_chan, + .remove_dev_channel = uhci_destroy_chan, + .modify_dev_channel = uhci_modify_chan, + .set_toggle = uhci_set_toggle, + .create_eop = uhci_create_eop, + .remove_eop = uhci_remove_eop +}; + +void +uhci_init (void) +{ + struct pci_dev *pd; + int dev_num; + + dev_num = 0; + while ((pd = pci_get_dev_by_class (PCI_MAJOR_SERIALBUS, PCI_MINOR_USB, + PCI_USB_IFACE_UHCI, dev_num)) != NULL) + { + struct pci_io *io; + struct uhci_info *ui; + int i; + + dev_num++; + + /* find IO space */ + io = NULL; + while ((io = pci_io_enum (pd, io)) != NULL) + { + if (pci_io_size (io) == UHCI_REGSZ) + break; + } + + /* not found, next PCI */ + if (io == NULL) + continue; + + ui = uhci_create_info (io); + ui->dev = pd; + + uhci_detect_ports (ui); + + pci_write_config16 (ui->dev, UHCI_REG_LEGSUP, 0x8f00); + pci_reg_write16 (ui->io, UHCI_REG_USBCMD, USB_CMD_HCRESET); + asm volatile ("mfence":::"memory"); + timer_usleep (5); + if (pci_reg_read16 (ui->io, UHCI_REG_USBCMD) & USB_CMD_HCRESET) + printf ("reset failed!\n"); + pci_reg_write16 (ui->io, UHCI_REG_USBINTR, 0); + pci_reg_write16 (ui->io, UHCI_REG_USBCMD, 0); + + pci_reg_write16 (ui->io, UHCI_REG_PORTSC1, 0); + pci_reg_write16 (ui->io, UHCI_REG_PORTSC2, 0); + + pci_reg_write8 (ui->io, UHCI_REG_SOFMOD, 64); + pci_reg_write32 (ui->io, UHCI_REG_FLBASEADD, vtop (ui->frame_list)); + pci_reg_write16 (ui->io, UHCI_REG_FRNUM, 0); + asm volatile ("mfence":::"memory"); + + /* deactivate SMM junk, only enable IRQ */ + pci_write_config16 (ui->dev, UHCI_REG_LEGSUP, 0x2000); + + asm volatile ("mfence":::"memory"); + + pci_reg_write16 (ui->io, UHCI_REG_USBCMD, + USB_CMD_RS | USB_CMD_CONFIGURE | USB_CMD_MAX_PACKET); + pci_reg_write16 (ui->io, UHCI_REG_USBINTR, + USB_INTR_SHORT | USB_INTR_IOC | + USB_INTR_TIMEOUT | USB_INTR_RESUME); + + asm volatile ("mfence":::"memory"); + + printf ("UHCI: Enabling %d root ports\n", ui->num_ports); + ui->attached_ports = 0; + for (i = 0; i < ui->num_ports; i++) + ui->attached_ports += uhci_enable_port (ui, i); + + pci_register_irq (pd, uhci_irq, ui); + + usb_register_host (&uhci_host, ui); + } +} + +#define UHCI_PORT_TIMEOUT 1000 +static int +uhci_enable_port (struct uhci_info *ui, int idx) +{ + uint16_t status; + int time, stable_since; + int count; + int port; + + port = UHCI_REG_PORTSC1 + idx * 2; + + status = 0xffff; + stable_since = 0; + for (time = 0; ; time += 25) + { + uint16_t new_status; + new_status = pci_reg_read16 (ui->io, port); + if (status != (new_status & USB_PORT_CONNECTSTATUS) + || new_status & USB_PORT_CONNECTCHG) + { + if (new_status & USB_PORT_CONNECTCHG) + pci_reg_write16 (ui->io, port, (new_status & ~0xe80a) | USB_PORT_CONNECTCHG); + stable_since = time; + status = new_status & USB_PORT_CONNECTSTATUS; + } + else if (time - stable_since >= 100) + break; + else if (time >= 1500) + return 0; + timer_msleep (25); + } + + if (!(status & USB_PORT_CONNECTSTATUS)) + return 0; + + for (count = 0; count < 3; count++) + { + status = pci_reg_read16 (ui->io, port) & ~0xe80a; + pci_reg_write16 (ui->io, port, status | USB_PORT_RESET); + timer_msleep (50); + + status = pci_reg_read16 (ui->io, port) & ~0xe80a; + pci_reg_write16 (ui->io, port, status & ~USB_PORT_RESET); + timer_usleep (10); + + status = pci_reg_read16 (ui->io, port) & ~0xe80a; + pci_reg_write16 (ui->io, port, status & ~(USB_PORT_CONNECTCHG | USB_PORT_CHANGE)); + + status = pci_reg_read16 (ui->io, port) & ~0xe80a; + pci_reg_write16 (ui->io, port, status | USB_PORT_ENABLE); + + status = pci_reg_read16 (ui->io, port); + if (status & USB_PORT_ENABLE) + break; + } + + if (!(status & USB_PORT_CONNECTSTATUS)) + { + pci_reg_write16 (ui->io, port, 0); + return 0; + } + + return 1; +} + + +static void +dump_regs (struct uhci_info *ui) +{ + int regs[] = { 0, 2, 4, 6, 8, 0xc, 0x10, 0x12 }; + int sz[] = { 2, 2, 2, 2, 4, 2, 2, 2 }; + char *name[] = + { "cmd", "sts", "intr", "frnum", "base", "sofmod", "portsc1", "portsc2" }; + int i; + printf ("UHCI registers:\n"); + for (i = 0; i < 8; i++) + { + printf ("%s: %x\n", name[i], (sz[i] == 2) ? + pci_reg_read16 (ui->io, regs[i]) : + pci_reg_read32 (ui->io, regs[i])); + + } +} + + +static void +uhci_destroy_info (struct uhci_info *ui) +{ + palloc_free_page (ui->frame_list); + palloc_free_page (ui->td_pool); + palloc_free_page (ui->qh_pool); + bitmap_destroy (ui->qh_used); + bitmap_destroy (ui->td_used); + free (ui); +} + +static struct uhci_info * +uhci_create_info (struct pci_io *io) +{ + struct uhci_info *ui; + int i; + + ui = malloc (sizeof (struct uhci_info)); + + ui->io = io; + lock_init (&ui->lock); + + /* create an empty schedule */ + ui->frame_list = palloc_get_page (PAL_ASSERT | PAL_NOCACHE); + memset (ui->frame_list, 0, PGSIZE); + for (i = 0; i < FRAME_LIST_ENTRIES; i++) + ui->frame_list[i].terminate = 1; + + /* permit 3 timeouts */ + ui->timeouts = 3; +// thread_create ("uhci watchdog", PRI_MIN, +// (thread_func *) uhci_stall_watchdog, ui); + ui->td_pool = palloc_get_page (PAL_ASSERT | PAL_NOCACHE); + ui->td_used = bitmap_create (TD_ENTRIES); + ui->qh_pool = palloc_get_page (PAL_ASSERT | PAL_NOCACHE); + ui->qh_used = bitmap_create (QH_ENTRIES); + sema_init (&ui->td_sem, TD_ENTRIES); + + list_init (&ui->devices); + list_init (&ui->waiting); + + return ui; +} + + +static int +uhci_tx_pkt (host_eop_info hei, int token, void *pkt, int min_sz, + int max_sz, int *in_sz, bool wait) +{ + struct uhci_eop_info *ue; + struct uhci_dev_info *ud; + + ASSERT (min_sz <= max_sz); + + /* can't have page overlap */ + if (pkt != NULL) + { + ASSERT (max_sz > 0); + ASSERT (pg_no (pkt + max_sz - 1) == pg_no (pkt)); + } + + ue = hei; + ud = ue->ud; + + /* don't bother if ports are down */ + if (ud->ui->attached_ports == 0) + { + return USB_HOST_ERR_NODEV; + } + + /* setup token acts to synchronize data toggle */ + if (token == USB_TOKEN_SETUP) + ue->toggle = 0; + + if (min_sz != 0) + { + return uhci_tx_pkt_bulk (ue, token, pkt, max_sz, in_sz); + } + else + { + if (wait == false) + { + if (in_sz != NULL) + *in_sz = max_sz; + return uhci_tx_pkt_now (ue, token, pkt, max_sz); + } + else + { + return uhci_tx_pkt_wait (ue, token, pkt, max_sz, in_sz); + } + } + + return 0; +} + +static int +uhci_tx_pkt_bulk (struct uhci_eop_info *ue, int token, void *buf, + int sz, int *tx) +{ + /* XXX this can be made to use async packets */ + int bytes = 0; + int txed = 0; + + /* send data in max_pkt sized chunks */ + while (bytes < sz) + { + int to_tx, pkt_txed; + int left; + int err; + bool wait_on_pkt; + + left = sz - bytes; + to_tx = (left > ue->maxpkt) ? ue->maxpkt : left; + wait_on_pkt = (left <= to_tx) ? true : false; + + pkt_txed = 0; + err = uhci_tx_pkt (ue, token, buf + bytes, 0, to_tx, &pkt_txed, + wait_on_pkt); + if (err) + { + if (tx != NULL) + *tx = txed; + return err; + } + txed += pkt_txed; + bytes += pkt_txed; + } + + if (tx != NULL) + *tx = txed; + + return USB_HOST_ERR_NONE; +} + +static int +token_to_pid (int token) +{ + switch (token) + { + case USB_TOKEN_SETUP: + return USB_PID_SETUP; + case USB_TOKEN_IN: + return USB_PID_IN; + case USB_TOKEN_OUT: + return USB_PID_OUT; + default: + PANIC ("Unknown USB token\n"); + } +} + +static void +uhci_setup_td (struct tx_descriptor *td, int dev_addr, int token, + int eop, void *pkt, int sz, int toggle, bool ls) +{ + td->buf_ptr = (sz == 0) ? 0 : vtop (pkt); + + td->token.pid = token_to_pid (token); + td->token.dev_addr = dev_addr; + td->token.end_point = eop; + td->token.data_toggle = toggle; + td->token.maxlen = sz - 1; +// td->control.ls = ls; + + td->control.actual_len = 0; + td->control.active = 1; + td->flp.qh_select = 0; + td->flp.depth_select = 0; + + /* kill packet if too many errors */ + td->control.error_limit = 3; +} + +static int +uhci_tx_pkt_now (struct uhci_eop_info *ue, int token, void *pkt, int sz) +{ + struct tx_descriptor *td; + struct uhci_dev_info *ud; + + ud = ue->ud; + + uhci_lock (ud->ui); + + td = uhci_acquire_td (ud->ui); + memset (td, 0, sizeof (struct tx_descriptor)); + uhci_setup_td (td, ud->dev_addr, token, ue->eop, pkt, sz, ue->toggle, + ud->low_speed); + td->control.ioc = 1; + + uhci_stop (ud->ui); + + uhci_add_td_to_qh (ud->qh, td); + td->flags = TD_FL_ASYNC | TD_FL_USED; + + uhci_run (ud->ui); + uhci_unlock (ud->ui); + + ue->toggle ^= 1; + return USB_HOST_ERR_NONE; +} + +static int +uhci_tx_pkt_wait (struct uhci_eop_info *ue, int token, void *pkt, + int max_sz, int *in_sz) +{ + enum intr_level old_lvl; + struct tx_descriptor *td; + struct usb_wait w; + int err; + struct uhci_dev_info *ud; + int n; + + ud = ue->ud; + + uhci_lock (ud->ui); + + td = uhci_acquire_td (ud->ui); + memset (td, 0, sizeof (struct tx_descriptor)); + + uhci_setup_td (td, ud->dev_addr, token, ue->eop, pkt, max_sz, ue->toggle, + ud->low_speed); + td->control.ioc = 1; + + w.td = td; + w.ud = ud; + sema_init (&w.sem, 0); + + uhci_stop (ud->ui); + + /* put into device's queue and add to waiting packet list */ + uhci_add_td_to_qh (ud->qh, td); + td->flags = TD_FL_USED; + + list_push_back (&ud->ui->waiting, &w.peers); + + /* reactivate controller and wait */ + old_lvl = intr_disable (); + uhci_run (ud->ui); + uhci_unlock (ud->ui); + sema_down (&w.sem); + intr_set_level (old_lvl); + + if (in_sz != NULL) + { + if (w.td->control.actual_len == 0x7ff) + *in_sz = 0; + else + *in_sz = w.td->control.actual_len + 1; + } + + if (w.td->control.bitstuff) + err = USB_HOST_ERR_BITSTUFF; + else if (w.td->control.timeout) + err = USB_HOST_ERR_TIMEOUT; + else if (w.td->control.nak) + err = USB_HOST_ERR_NAK; + else if (w.td->control.babble) + err = USB_HOST_ERR_BABBLE; + else if (w.td->control.buffer_error) + err = USB_HOST_ERR_BUFFER; + else if (w.td->control.stalled) + err = USB_HOST_ERR_STALL; + else + { + err = USB_HOST_ERR_NONE; + ue->toggle ^= 1; + } + + uhci_release_td (ud->ui, td); + +#if 0 + printf ("%s to device %d:\n", + token == USB_TOKEN_OUT ? "OUT" + : token == USB_TOKEN_IN ? "IN" + : token == USB_TOKEN_SETUP ? "SETUP" + : "unknown", + ud->dev_addr); + n = (w.td->control.actual_len + 1) & 0x7ff; + hex_dump (0, pkt, n, true); + if (err) + printf ("tx_pkt_wait: err=%d\n", err); +#endif + + return err; +} + +static void +uhci_add_td_to_qh (struct queue_head *qh, struct tx_descriptor *td) +{ + struct frame_list_ptr *fp; + + ASSERT (td != NULL); + + td->head = &qh->qelp; + if (qh->qelp.terminate == 1) + { + /* queue is empty */ + td->flp.terminate = 1; + barrier (); + td->flp.flp = 0; + qh->qelp.flp = ptr_to_flp (vtop (td)); + qh->qelp.terminate = 0; + } + else + { + /* find the last element in the queue */ + fp = ptov (flp_to_ptr (qh->qelp.flp)); + ASSERT (qh->qelp.terminate == 0); + while (!fp->terminate) + { + fp = ptov (flp_to_ptr (fp->flp)); + } + + /* set TD to terminated ptr */ + td->flp = *fp; + + fp->qh_select = 0; + fp->depth_select = 0; + fp->flp = ptr_to_flp (vtop (td)); + barrier (); + fp->terminate = 0; + } +} + +static void +uhci_irq (void *uhci_data) +{ + struct uhci_info *ui; + uint16_t status; + + ui = uhci_data; + status = pci_reg_read16 (ui->io, UHCI_REG_USBSTS); + if (status & USB_STATUS_PROCESSERR) + { + dump_all_qh (ui); + dump_regs (ui); + PANIC ("UHCI: Malformed schedule"); + } + else if (status & USB_STATUS_HOSTERR) + { + dump_all_qh (ui); + dump_regs (ui); + PANIC ("UHCI: Host system error"); + } + else if (status & USB_STATUS_INTERR) + { + /* errors */ + pci_reg_write16 (ui->io, UHCI_REG_USBSTS, USB_STATUS_INTERR); + } + + if (status & USB_STATUS_USBINT) + { + /* turn off interrupt */ + uhci_stop_unlocked (ui); + pci_reg_write16 (ui->io, UHCI_REG_USBSTS, USB_STATUS_USBINT); + uhci_process_completed (ui); + uhci_run_unlocked (ui); + } +} + +static int +uhci_process_completed (struct uhci_info *ui) +{ + struct list_elem *li; + int completed = 0; + size_t start = 0; + + li = list_begin (&ui->waiting); + while (li != list_end (&ui->waiting)) + { + struct usb_wait *uw; + struct list_elem *next; + + next = list_next (li); + uw = list_entry (li, struct usb_wait, peers); + + if (!uw->td->control.active) + { + list_remove (li); + if (uw->td->control.error_limit == 0 || uw->td->control.stalled) + { + uhci_remove_error_td (uw->td); + } + uw->td->flags = 0; + sema_up (&uw->sem); + completed++; + } + li = next; + } + + /* must be a completed async TD.. */ + /* is this too time consuming? I hope not */ + if (completed != 0) + return completed; + + while (start < TD_ENTRIES) + { + struct tx_descriptor *td; + + start = bitmap_scan (ui->td_used, start, 1, true); + if (start == BITMAP_ERROR) + break; + + td = td_from_pool (ui, start); + + if (!td->control.active && (td->flags & TD_FL_ASYNC) && + (td->flags & TD_FL_USED)) + { + if (td->control.error_limit == 0 || td->control.stalled) + { + uhci_remove_error_td (td); + } + uhci_release_td (ui, td); + completed++; + } + start++; + } + + return completed; +} + +static void +uhci_remove_error_td (struct tx_descriptor *td) +{ + struct frame_list_ptr *fp; + uint32_t td_flp; + + ASSERT (td->head != NULL); + + fp = td->head; + td_flp = ptr_to_flp (vtop (td)); + while (fp->flp != td_flp) + { + ASSERT (fp->terminate == 0); + fp = ptov (flp_to_ptr (fp->flp)); + } + *fp = td->flp; +} + +static int +uhci_detect_change (host_info hi) +{ + struct uhci_info *ui; + int change; + int i; + + ui = hi; + change = 0; + uhci_lock (ui); + for (i = 0; i < ui->num_ports; i++) + { + change = check_and_flip_change (ui, i); + if (change != 0) + break; + } + uhci_unlock (ui); + + return change; +} + +static int +check_and_flip_change (struct uhci_info *ui, int port) +{ + int val; + int reg; + + reg = UHCI_REG_PORTSC1 + port * 2; + val = pci_reg_read16 (ui->io, reg); + if (val & USB_PORT_CHANGE) + { + pci_reg_write16 (ui->io, reg, val & ~(USB_PORT_CHANGE)); + return 1; + } + + return 0; +} + +static host_dev_info +uhci_create_chan (host_info hi, int dev_addr, int ver) +{ + struct uhci_info *ui; + struct uhci_dev_info *ud; + int i; + + ASSERT (dev_addr <= 127 && dev_addr >= 0); + + ui = hi; + + ud = malloc (sizeof (struct uhci_dev_info)); + ud->dev_addr = dev_addr; + ud->low_speed = (ver == USB_VERSION_1_0) ? true : false; + + ud->errors = 0; + ud->ui = ui; + lock_init (&ud->lock); + + uhci_lock (ui); + + ud->qh = qh_alloc (ud->ui); + + /* queue data */ + memset (ud->qh, 0, sizeof (*ud->qh)); + ud->qh->qelp.terminate = 1; + barrier (); + ud->qh->qelp.flp = 0; + ud->qh->qelp.qh_select = 0; + ud->qh->qhlp.qh_select = 1; + + uhci_stop (ui); + + /* add to queues in frame list */ + ud->qh->qhlp.flp = ui->frame_list[0].flp; + ud->qh->qhlp.terminate = ui->frame_list[0].terminate; + for (i = 0; i < FRAME_LIST_ENTRIES; i++) + { + ui->frame_list[i].flp = ptr_to_flp (vtop (ud->qh)); + ui->frame_list[i].qh_select = 1; + ui->frame_list[i].terminate = 0; + } + + /* add to device list */ + list_push_back (&ui->devices, &ud->peers); + + uhci_run (ui); + uhci_unlock (ui); + + return ud; +} + +static void +uhci_destroy_chan (host_dev_info hd) +{ + struct uhci_dev_info *ud; + struct list_elem *li; + + ud = hd; + uhci_lock (ud->ui); + + uhci_stop (ud->ui); + + uhci_remove_qh (ud->ui, ud->qh); + + /* wake up all waiting */ + li = list_begin (&ud->ui->waiting); + while (li != list_end (&ud->ui->waiting)) + { + struct usb_wait *w; + w = list_entry (li, struct usb_wait, peers); + if (w->ud == ud) + { + sema_up (&w->sem); + list_remove (li); + } + li = list_next (li); + } + + list_remove (&ud->peers); + + uhci_run (ud->ui); + + qh_free (ud->ui, ud->qh); + + uhci_unlock (ud->ui); + + free (ud); +} + +/** + * Remove a queue from the UHCI schedule + */ +static void +uhci_remove_qh (struct uhci_info *ui, struct queue_head *qh) +{ + uintptr_t qh_flp; + + ASSERT (lock_held_by_current_thread (&ui->lock)); + ASSERT (uhci_is_stopped (ui)); + ASSERT (qh != NULL); + + qh_flp = ptr_to_flp (vtop (qh)); + /* remove from host queue */ + if (ui->frame_list[0].flp == qh_flp) + { + int i; + /* up top */ + for (i = 0; i < FRAME_LIST_ENTRIES; i++) + { + ui->frame_list[i] = qh->qhlp; + } + } + else + { + /* in the middle */ + struct frame_list_ptr *fp; + struct frame_list_ptr *prev; + + fp = ptov (flp_to_ptr (ui->frame_list[0].flp)); + ASSERT (!fp->terminate); + do + { + prev = fp; + fp = ptov (flp_to_ptr (fp->flp)); + } + while (!fp->terminate && fp->flp != qh_flp); + *prev = qh->qhlp; + } +} + +/** + * Put UHCI into stop state + * Wait until status register reflects setting + */ +static void +uhci_stop (struct uhci_info *ui) +{ + ASSERT (intr_get_level () != INTR_OFF); + ASSERT (lock_held_by_current_thread (&ui->lock)); + + uhci_stop_unlocked (ui); +} + +static void +uhci_stop_unlocked (struct uhci_info *ui) +{ + uint16_t cmd; + int i; + + cmd = pci_reg_read16 (ui->io, UHCI_REG_USBCMD); + cmd = cmd & ~USB_CMD_RS; + + pci_reg_write16 (ui->io, UHCI_REG_USBCMD, cmd); + + /* wait for execution schedule to finish up */ + for (i = 0; i < 1000; i++) + { + if (uhci_is_stopped (ui)) + return; + } + + PANIC ("UHCI: Controller did not halt\n"); + +} + +static void +uhci_run_unlocked (struct uhci_info *ui) +{ + uint16_t cmd; + cmd = pci_reg_read16 (ui->io, UHCI_REG_USBCMD); + cmd = cmd | USB_CMD_RS | USB_CMD_MAX_PACKET; + pci_reg_write16 (ui->io, UHCI_REG_USBCMD, cmd); +} + +/** + * Put UHCI into 'Run' State + */ +static void +uhci_run (struct uhci_info *ui) +{ + ASSERT (lock_held_by_current_thread (&ui->lock)); + uhci_run_unlocked (ui); +} + +static struct tx_descriptor * +uhci_acquire_td (struct uhci_info *ui) +{ + size_t td_idx; + struct tx_descriptor *td; + + ASSERT (lock_held_by_current_thread (&ui->lock)); + ASSERT (!uhci_is_stopped (ui)); + + sema_down (&ui->td_sem); + td_idx = bitmap_scan_and_flip (ui->td_used, 0, 1, false); + ASSERT (td_idx != BITMAP_ERROR); + td = td_from_pool (ui, td_idx); + + return td; +} + +static void +uhci_modify_chan (host_dev_info hd, int dev_addr, int ver) +{ + struct uhci_dev_info *ud; + + ud = hd; + ud->dev_addr = dev_addr; + ud->low_speed = (ver == USB_VERSION_1_0) ? true : false; +} + +static void +uhci_set_toggle (host_eop_info he, int toggle) +{ + struct uhci_eop_info *ue; + + ue = he; + ue->toggle = toggle; +} + +static void +dump_all_qh (struct uhci_info *ui) +{ + struct list_elem *li; + + printf ("schedule: %x...", vtop (ui->frame_list)); + printf ("%x", *((uint32_t *) ui->frame_list)); + li = list_begin (&ui->devices); + while (li != list_end (&ui->devices)) + { + struct uhci_dev_info *ud; + ud = list_entry (li, struct uhci_dev_info, peers); + dump_qh (ud->qh); + li = list_next (li); + } +} + +static void +dump_qh (struct queue_head *qh) +{ + struct frame_list_ptr *fp; + printf ("qh: %p %x\n", qh, vtop (qh)); + fp = &qh->qelp; + while (!fp->terminate) + { + printf ("%x %x\n", *((uint32_t *) fp), *(uint32_t *) (fp + 1)); + fp = ptov (flp_to_ptr (fp->flp)); + } + printf ("%x %x\n\n", *(uint32_t *) fp, *(uint32_t *) (fp + 1)); +} + +static struct tx_descriptor * +td_from_pool (struct uhci_info *ui, int idx) +{ + ASSERT (idx >= 0 && idx < TD_ENTRIES); + return (((void *) ui->td_pool) + idx * 32); +} + +static void +uhci_detect_ports (struct uhci_info *ui) +{ + int i; + ui->num_ports = 0; + for (i = 0; i < UHCI_MAX_PORTS; i++) + { + uint16_t status; + status = pci_reg_read16 (ui->io, UHCI_REG_PORTSC1 + i * 2); + if (!(status & 0x0080) || status == 0xffff) + return; + ui->num_ports++; + } +} + +static void +uhci_stall_watchdog (struct uhci_info *ui) +{ + while (1) + { + int rmved; + timer_msleep (1000); + printf ("watchdog\n"); + uhci_lock (ui); + uhci_stop (ui); + rmved = uhci_remove_stalled (ui); + if (rmved > 0) + printf ("removed stalled packet in watchdog\n"); + uhci_run (ui); + uhci_unlock (ui); + } +} + +static int +uhci_remove_stalled (struct uhci_info *ui) +{ + struct list_elem *li; + int rmved; + + rmved = 0; + li = list_begin (&ui->waiting); + + intr_disable (); + + while (li != list_end (&ui->waiting)) + { + struct usb_wait *uw; + struct list_elem *next; + uint32_t ctrl; + + next = list_next (li); + uw = list_entry (li, struct usb_wait, peers); + + if ((!uw->td->control.active && uw->td->control.stalled) || + (uw->td->control.nak)) + { + memcpy (&ctrl, &uw->td->control, 4); + printf ("CTRL: %x\n", ctrl); + list_remove (li); + uhci_remove_error_td (uw->td); + sema_up (&uw->sem); + rmved++; + } + li = next; + } + + intr_enable (); + + return rmved; +} + +static void +uhci_release_td (struct uhci_info *ui, struct tx_descriptor *td) +{ + int ofs = (uintptr_t) td - (uintptr_t) ui->td_pool; + int entry = ofs / 32; + + ASSERT (entry < TD_ENTRIES); + + td->flags = 0; + bitmap_reset (ui->td_used, entry); + sema_up (&ui->td_sem); +} + +static host_eop_info +uhci_create_eop (host_dev_info hd, int eop, int maxpkt) +{ + struct uhci_dev_info *ud; + struct uhci_eop_info *e; + + ud = hd; + + e = malloc (sizeof (struct uhci_eop_info)); + e->eop = eop; + e->ud = ud; + e->maxpkt = maxpkt; + e->toggle = 0; + + return e; +} + +static void +uhci_remove_eop (host_eop_info hei) +{ + free (hei); +} + +static struct queue_head * +qh_alloc (struct uhci_info *ui) +{ + size_t qh_idx; + struct queue_head *qh; + + ASSERT (lock_held_by_current_thread (&ui->lock)); + + qh_idx = bitmap_scan_and_flip (ui->qh_used, 0, 1, false); + if (qh_idx == BITMAP_ERROR) + { + PANIC ("UHCI: Too many queue heads in use-- runaway USB stack?\n"); + } + qh = (void *) (((intptr_t) ui->qh_pool) + qh_idx * 16); + + return qh; +} + +static void +qh_free (struct uhci_info *ui, struct queue_head *qh) +{ + size_t entry; + ASSERT (lock_held_by_current_thread (&ui->lock)); + + entry = ((intptr_t) qh - (intptr_t) ui->qh_pool) / 16; + bitmap_reset (ui->qh_used, entry); +} diff --git a/src/devices/vga.c b/src/devices/vga.c index 8255747..f421b61 100644 --- a/src/devices/vga.c +++ b/src/devices/vga.c @@ -3,6 +3,7 @@ #include #include #include +#include "devices/speaker.h" #include "threads/io.h" #include "threads/interrupt.h" #include "threads/vaddr.h" @@ -80,6 +81,12 @@ vga_putc (int c) if (cx >= COL_CNT) newline (); break; + + case '\a': + intr_set_level (old_level); + speaker_beep (); + intr_disable (); + break; default: fb[cy][cx][0] = c; diff --git a/src/filesys/Make.vars b/src/filesys/Make.vars index d8050cd..b3aa005 100644 --- a/src/filesys/Make.vars +++ b/src/filesys/Make.vars @@ -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 diff --git a/src/filesys/directory.c b/src/filesys/directory.c index 0d265d5..030c1c9 100644 --- a/src/filesys/directory.c +++ b/src/filesys/directory.c @@ -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); diff --git a/src/filesys/directory.h b/src/filesys/directory.h index 7955937..930acf9 100644 --- a/src/filesys/directory.h +++ b/src/filesys/directory.h @@ -3,7 +3,7 @@ #include #include -#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]); diff --git a/src/filesys/filesys.c b/src/filesys/filesys.c index fedda08..7a53f5f 100644 --- a/src/filesys/filesys.c +++ b/src/filesys/filesys.c @@ -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) diff --git a/src/filesys/filesys.h b/src/filesys/filesys.h index caef83c..c1cda84 100644 --- a/src/filesys/filesys.h +++ b/src/filesys/filesys.h @@ -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); diff --git a/src/filesys/free-map.c b/src/filesys/free-map.c index 1cd9175..34f2060 100644 --- a/src/filesys/free-map.c +++ b/src/filesys/free-map.c @@ -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); diff --git a/src/filesys/free-map.h b/src/filesys/free-map.h index ce08f5c..316cd1c 100644 --- a/src/filesys/free-map.h +++ b/src/filesys/free-map.h @@ -3,7 +3,7 @@ #include #include -#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 */ diff --git a/src/filesys/fsutil.c b/src/filesys/fsutil.c index 14a4507..daa1488 100644 --- a/src/filesys/fsutil.c +++ b/src/filesys/fsutil.c @@ -3,10 +3,10 @@ #include #include #include +#include #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" @@ -66,98 +66,118 @@ fsutil_rm (char **argv) PANIC ("%s: delete failed\n", file_name); } -/* Copies from the "scratch" disk, hdc or hd1:0 to file ARGV[1] - in the file system. - - The current sector on the scratch disk must begin with the - string "PUT\0" followed by a 32-bit little-endian integer - indicating the file size in bytes. Subsequent sectors hold - the file content. - - The first call to this function will read starting at the - beginning of the scratch disk. Later calls advance across the - disk. This disk position is independent of that used for - fsutil_get(), so all `put's should precede all `get's. */ +/* Extracts a ustar-format tar archive from the scratch device + into the Pintos file system. */ void -fsutil_put (char **argv) +fsutil_extract (char **argv UNUSED) { - static disk_sector_t sector = 0; - - const char *file_name = argv[1]; - struct disk *src; - struct file *dst; - off_t size; - void *buffer; + static block_sector_t sector = 0; - printf ("Putting '%s' into the file system...\n", file_name); + struct block *src; + void *header, *data; - /* Allocate buffer. */ - buffer = malloc (DISK_SECTOR_SIZE); - if (buffer == NULL) - PANIC ("couldn't allocate buffer"); + /* Allocate buffers. */ + header = malloc (BLOCK_SECTOR_SIZE); + data = malloc (BLOCK_SECTOR_SIZE); + if (header == NULL || data == NULL) + PANIC ("couldn't allocate buffers"); - /* Open source disk and read file size. */ - src = disk_get (1, 0); + /* Open source device. */ + src = block_get_role (BLOCK_SCRATCH); if (src == NULL) - PANIC ("couldn't open source disk (hdc or hd1:0)"); - - /* Read file size. */ - disk_read (src, sector++, buffer); - if (memcmp (buffer, "PUT", 4)) - PANIC ("%s: missing PUT signature on scratch disk", file_name); - size = ((int32_t *) buffer)[1]; - if (size < 0) - PANIC ("%s: invalid file size %d", file_name, size); - - /* Create destination file. */ - if (!filesys_create (file_name, size)) - PANIC ("%s: create failed", file_name); - dst = filesys_open (file_name); - if (dst == NULL) - PANIC ("%s: open failed", file_name); + PANIC ("couldn't open scratch device"); - /* Do copy. */ - while (size > 0) + printf ("Extracting ustar archive from %s into file system...\n", + block_name (src)); + + for (;;) { - int chunk_size = size > DISK_SECTOR_SIZE ? DISK_SECTOR_SIZE : size; - disk_read (src, sector++, buffer); - if (file_write (dst, buffer, chunk_size) != chunk_size) - PANIC ("%s: write failed with %"PROTd" bytes unwritten", - file_name, size); - size -= chunk_size; + const char *file_name; + const char *error; + enum ustar_type type; + int size; + + /* Read and parse ustar header. */ + block_read (src, sector++, header); + error = ustar_parse_header (header, &file_name, &type, &size); + if (error != NULL) + PANIC ("%s: bad ustar header in sector %"PRDSNu" (%s)", + block_name (src), sector - 1, error); + + if (type == USTAR_EOF) + { + /* End of archive. */ + break; + } + else if (type == USTAR_DIRECTORY) + printf ("ignoring directory %s\n", file_name); + else if (type == USTAR_REGULAR) + { + struct file *dst; + + printf ("Putting '%s' into the file system...\n", file_name); + + /* Create destination file. */ + if (!filesys_create (file_name, size)) + PANIC ("%s: create failed", file_name); + dst = filesys_open (file_name); + if (dst == NULL) + PANIC ("%s: open failed", file_name); + + /* Do copy. */ + while (size > 0) + { + int chunk_size = (size > BLOCK_SECTOR_SIZE + ? BLOCK_SECTOR_SIZE + : size); + block_read (src, sector++, data); + if (file_write (dst, data, chunk_size) != chunk_size) + PANIC ("%s: write failed with %"PROTd" bytes unwritten", + file_name, size); + size -= chunk_size; + } + + /* Finish up. */ + file_close (dst); + } } - /* Finish up. */ - file_close (dst); - free (buffer); + /* Erase the ustar header from the start of the 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, 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. - - The current sector on the scratch disk will receive "GET\0" - followed by the file's size in bytes as a 32-bit, - little-endian integer. Subsequent sectors receive the file's - data. +/* Copies file FILE_NAME from the Pintos file system to a + ustar-format tar archive maintained on the scratch device. The first call to this function will write starting at the - beginning of the scratch disk. Later calls advance across the - disk. This disk position is independent of that used for - fsutil_put(), so all `put's should precede all `get's. */ + beginning of the scratch device, thus creating a new archive. + Therefore, any `extract' calls must precede all `append's. + Later calls advance across the device, appending to the + archive. */ void -fsutil_get (char **argv) +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 ("Getting '%s' from the file system...\n", file_name); /* Allocate buffer. */ - buffer = malloc (DISK_SECTOR_SIZE); + buffer = malloc (BLOCK_SECTOR_SIZE); if (buffer == NULL) PANIC ("couldn't allocate buffer"); @@ -167,30 +187,36 @@ fsutil_get (char **argv) PANIC ("%s: open failed", file_name); size = file_length (src); - /* Open target disk. */ - dst = disk_get (1, 0); + /* Open target 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 size to sector 0. */ - memset (buffer, 0, DISK_SECTOR_SIZE); - memcpy (buffer, "GET", 4); - ((int32_t *) buffer)[1] = size; - disk_write (dst, sector++, buffer); + /* Write ustar header to first sector. */ + if (!ustar_make_header (file_name, USTAR_REGULAR, size, buffer)) + PANIC ("%s: can't get from file system (name too long)", file_name); + 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 get. */ + memset (buffer, 0, BLOCK_SECTOR_SIZE); + block_write (dst, sector, buffer); + block_write (dst, sector, buffer + 1); + /* Finish up. */ file_close (src); free (buffer); diff --git a/src/filesys/fsutil.h b/src/filesys/fsutil.h index abebfe2..cc73705 100644 --- a/src/filesys/fsutil.h +++ b/src/filesys/fsutil.h @@ -4,7 +4,7 @@ void fsutil_ls (char **argv); void fsutil_cat (char **argv); void fsutil_rm (char **argv); -void fsutil_put (char **argv); -void fsutil_get (char **argv); +void fsutil_extract (char **argv); +void fsutil_append (char **argv); #endif /* filesys/fsutil.h */ diff --git a/src/filesys/inode.c b/src/filesys/inode.c index d890ed8..3463563 100644 --- a/src/filesys/inode.c +++ b/src/filesys/inode.c @@ -11,10 +11,10 @@ #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. */ diff --git a/src/filesys/inode.h b/src/filesys/inode.h index be7df63..cb42310 100644 --- a/src/filesys/inode.h +++ b/src/filesys/inode.h @@ -3,15 +3,15 @@ #include #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); diff --git a/src/lib/debug.c b/src/lib/debug.c index 6d7c9e1..1dc1bf8 100644 --- a/src/lib/debug.c +++ b/src/lib/debug.c @@ -17,7 +17,7 @@ debug_backtrace (void) printf ("Call stack:"); for (frame = __builtin_frame_address (0); - frame != NULL && frame[0] != NULL; + (uintptr_t) frame >= 0x1000 && frame[0] != NULL; frame = frame[0]) printf (" %p", frame[1]); printf (".\n"); diff --git a/src/lib/endian.h b/src/lib/endian.h new file mode 100644 index 0000000..7e5fc32 --- /dev/null +++ b/src/lib/endian.h @@ -0,0 +1,19 @@ +#ifndef __LIB_ENDIAN_H +#define __LIB_ENDIAN_H + + +#define endian_swap32(x) ((((x) & 0xff000000) >> 24) | \ + (((x) & 0x00ff0000) >> 8) | \ + (((x) & 0x0000ff00) << 8) | \ + (((x) & 0x000000ff) << 24)) +#define endian_swap16(x) ((((x) & 0xff00)>> 8) | (((x) & 0x00ff) << 8)) +#define endian_swap24(x) (((x) >> 16) & 0xff) | (((x) & 0xff) << 16) | ((x) & 0xff00) + +#define be32_to_machine(x) endian_swap32(x) +#define be16_to_machine(x) endian_swap16(x) +#define be24_to_machine(x) endian_swap24(x) +#define machine_to_be32(x) endian_swap32(x) +#define machine_to_be16(x) endian_swap16(x) +#define machine_to_be24(x) endian_swap24(x) + +#endif diff --git a/src/lib/kernel/list.h b/src/lib/kernel/list.h index 2388f9a..82efbb5 100644 --- a/src/lib/kernel/list.h +++ b/src/lib/kernel/list.h @@ -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. */ diff --git a/src/lib/packed.h b/src/lib/packed.h new file mode 100644 index 0000000..9a9b6e2 --- /dev/null +++ b/src/lib/packed.h @@ -0,0 +1,10 @@ +#ifndef __LIB_PACKED_H +#define __LIB_PACKED_H + +/* The "packed" attribute, when applied to a structure, prevents + GCC from inserting padding bytes between or after structure + members. It must be specified at the time of the structure's + definition, normally just after the closing brace. */ +#define PACKED __attribute__ ((packed)) + +#endif /* lib/packed.h */ diff --git a/src/lib/stdio.c b/src/lib/stdio.c index af80e51..8927c50 100644 --- a/src/lib/stdio.c +++ b/src/lib/stdio.c @@ -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); + } +} diff --git a/src/lib/stdio.h b/src/lib/stdio.h index 8288ff0..2739c0a 100644 --- a/src/lib/stdio.h +++ b/src/lib/stdio.h @@ -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, diff --git a/src/lib/ustar.c b/src/lib/ustar.c new file mode 100644 index 0000000..49af69a --- /dev/null +++ b/src/lib/ustar.c @@ -0,0 +1,228 @@ +#include +#include +#include +#include +#include + +/* Header for ustar-format tar archive. See the documentation of + the "pax" utility in [SUSv3] for the the "ustar" format + specification. */ +struct ustar_header + { + char name[100]; /* File name. Null-terminated if room. */ + char mode[8]; /* Permissions as octal string. */ + char uid[8]; /* User ID as octal string. */ + char gid[8]; /* Group ID as octal string. */ + char size[12]; /* File size in bytes as octal string. */ + char mtime[12]; /* Modification time in seconds + from Jan 1, 1970, as octal string. */ + char chksum[8]; /* Sum of octets in header as octal string. */ + char typeflag; /* An enum ustar_type value. */ + char linkname[100]; /* Name of link target. + Null-terminated if room. */ + char magic[6]; /* "ustar\0" */ + char version[2]; /* "00" */ + char uname[32]; /* User name, always null-terminated. */ + char gname[32]; /* Group name, always null-terminated. */ + char devmajor[8]; /* Device major number as octal string. */ + char devminor[8]; /* Device minor number as octal string. */ + char prefix[155]; /* Prefix to file name. + Null-terminated if room. */ + char padding[12]; /* Pad to 512 bytes. */ + } +PACKED; + +/* Returns the checksum for the given ustar format HEADER. */ +static unsigned int +calculate_chksum (const struct ustar_header *h) +{ + const uint8_t *header = (const uint8_t *) h; + unsigned int chksum; + size_t i; + + chksum = 0; + for (i = 0; i < USTAR_HEADER_SIZE; i++) + { + /* The ustar checksum is calculated as if the chksum field + were all spaces. */ + const size_t chksum_start = offsetof (struct ustar_header, chksum); + const size_t chksum_end = chksum_start + sizeof h->chksum; + bool in_chksum_field = i >= chksum_start && i < chksum_end; + chksum += in_chksum_field ? ' ' : header[i]; + } + return chksum; +} + +/* Drop possibly dangerous prefixes from FILE_NAME and return the + stripped name. An archive with file names that start with "/" + or "../" could cause a naive tar extractor to write to + arbitrary parts of the file system, not just the destination + directory. We don't want to create such archives or be such a + naive extractor. + + The return value can be a suffix of FILE_NAME or a string + literal. */ +static const char * +strip_antisocial_prefixes (const char *file_name) +{ + while (*file_name == '/' + || !memcmp (file_name, "./", 2) + || !memcmp (file_name, "../", 3)) + file_name = strchr (file_name, '/') + 1; + return *file_name == '\0' || !strcmp (file_name, "..") ? "." : file_name; +} + +/* Composes HEADER as a USTAR_HEADER_SIZE (512)-byte archive + header in ustar format for a SIZE-byte file named FILE_NAME of + the given TYPE. The caller is responsible for writing the + header to a file or device. + + If successful, returns true. On failure (due to an + excessively long file name), returns false. */ +bool +ustar_make_header (const char *file_name, enum ustar_type type, + int size, char header[USTAR_HEADER_SIZE]) +{ + struct ustar_header *h = (struct ustar_header *) header; + + ASSERT (sizeof (struct ustar_header) == USTAR_HEADER_SIZE); + ASSERT (type == USTAR_REGULAR || type == USTAR_DIRECTORY); + + /* Check file name. */ + file_name = strip_antisocial_prefixes (file_name); + if (strlen (file_name) > 99) + { + printf ("%s: file name too long\n", file_name); + return false; + } + + /* Fill in header except for final checksum. */ + memset (h, 0, sizeof *h); + strlcpy (h->name, file_name, sizeof h->name); + snprintf (h->mode, sizeof h->mode, "%07o", + type == USTAR_REGULAR ? 0644 : 0755); + strlcpy (h->uid, "0000000", sizeof h->uid); + strlcpy (h->gid, "0000000", sizeof h->gid); + snprintf (h->size, sizeof h->size, "%011o", size); + snprintf (h->mtime, sizeof h->size, "%011o", 1136102400); + h->typeflag = type; + strlcpy (h->magic, "ustar", sizeof h->magic); + h->version[0] = h->version[1] = '0'; + strlcpy (h->gname, "root", sizeof h->gname); + strlcpy (h->uname, "root", sizeof h->uname); + + /* Compute and fill in final checksum. */ + snprintf (h->chksum, sizeof h->chksum, "%07o", calculate_chksum (h)); + + return true; +} + +/* Parses a SIZE-byte octal field in S in the format used by + ustar format. If successful, stores the field's value in + *VALUE and returns true; on failure, returns false. + + ustar octal fields consist of a sequence of octal digits + terminated by a space or a null byte. The ustar specification + seems ambiguous as to whether these fields must be padded on + the left with '0's, so we accept any field that fits in the + available space, regardless of whether it fills the space. */ +static bool +parse_octal_field (const char *s, size_t size, unsigned long int *value) +{ + size_t ofs; + + *value = 0; + for (ofs = 0; ofs < size; ofs++) + { + char c = s[ofs]; + if (c >= '0' && c <= '7') + { + if (*value > ULONG_MAX / 8) + { + /* Overflow. */ + return false; + } + *value = c - '0' + *value * 8; + } + else if (c == ' ' || c == '\0') + { + /* End of field, but disallow completely empty + fields. */ + return ofs > 0; + } + else + { + /* Bad character. */ + return false; + } + } + + /* Field did not end in space or null byte. */ + return false; +} + +/* Returns true if the CNT bytes starting at BLOCK are all zero, + false otherwise. */ +static bool +is_all_zeros (const char *block, size_t cnt) +{ + while (cnt-- > 0) + if (*block++ != 0) + return false; + return true; +} + +/* Parses HEADER as a ustar-format archive header for a regular + file or directory. If successful, stores the archived file's + name in *FILE_NAME (as a pointer into HEADER or a string + literal), its type in *TYPE, and its size in bytes in *SIZE, + and returns a null pointer. On failure, returns a + human-readable error message. */ +const char * +ustar_parse_header (const char header[USTAR_HEADER_SIZE], + const char **file_name, enum ustar_type *type, int *size) +{ + const struct ustar_header *h = (const struct ustar_header *) header; + unsigned long int chksum, size_ul; + + ASSERT (sizeof (struct ustar_header) == USTAR_HEADER_SIZE); + + /* Detect end of archive. */ + if (is_all_zeros (header, USTAR_HEADER_SIZE)) + { + *file_name = NULL; + *type = USTAR_EOF; + *size = 0; + return NULL; + } + + /* Validate ustar header. */ + if (memcmp (h->magic, "ustar", 6)) + return "not a ustar archive"; + else if (h->version[0] != '0' || h->version[1] != '0') + return "invalid ustar version"; + else if (!parse_octal_field (h->chksum, sizeof h->chksum, &chksum)) + return "corrupt chksum field"; + else if (chksum != calculate_chksum (h)) + return "checksum mismatch"; + else if (h->name[sizeof h->name - 1] != '\0' || h->prefix[0] != '\0') + return "file name too long"; + else if (h->typeflag != USTAR_REGULAR && h->typeflag != USTAR_DIRECTORY) + return "unimplemented file type"; + if (h->typeflag == USTAR_REGULAR) + { + if (!parse_octal_field (h->size, sizeof h->size, &size_ul)) + return "corrupt file size field"; + else if (size_ul > INT_MAX) + return "file too large"; + } + else + size_ul = 0; + + /* Success. */ + *file_name = strip_antisocial_prefixes (h->name); + *type = h->typeflag; + *size = size_ul; + return NULL; +} + diff --git a/src/lib/ustar.h b/src/lib/ustar.h new file mode 100644 index 0000000..43a5513 --- /dev/null +++ b/src/lib/ustar.h @@ -0,0 +1,29 @@ +#ifndef __LIB_USTAR_H +#define __LIB_USTAR_H + +/* Support for the standard Posix "ustar" format. See the + documentation of the "pax" utility in [SUSv3] for the the + "ustar" format specification. */ + +#include + +/* Type of a file entry in an archive. + The values here are the bytes that appear in the file format. + Only types of interest to Pintos are listed here. */ +enum ustar_type + { + USTAR_REGULAR = '0', /* Ordinary file. */ + USTAR_DIRECTORY = '5', /* Directory. */ + USTAR_EOF = -1 /* End of archive (not an official value). */ + }; + +/* Size of a ustar archive header, in bytes. */ +#define USTAR_HEADER_SIZE 512 + +bool ustar_make_header (const char *file_name, enum ustar_type, + int size, char header[USTAR_HEADER_SIZE]); +const char *ustar_parse_header (const char header[USTAR_HEADER_SIZE], + const char **file_name, + enum ustar_type *, int *size); + +#endif /* lib/ustar.h */ diff --git a/src/tests/Make.tests b/src/tests/Make.tests index 76d63f6..358e697 100644 --- a/src/tests/Make.tests +++ b/src/tests/Make.tests @@ -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 diff --git a/src/tests/filesys/extended/Make.tests b/src/tests/filesys/extended/Make.tests index 75a872b..e03b98d 100644 --- a/src/tests/filesys/extended/Make.tests +++ b/src/tests/filesys/extended/Make.tests @@ -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 diff --git a/src/tests/filesys/extended/tar.c b/src/tests/filesys/extended/tar.c index 6f70d97..9801484 100644 --- a/src/tests/filesys/extended/tar.c +++ b/src/tests/filesys/extended/tar.c @@ -2,6 +2,7 @@ Creates a tar archive. */ +#include #include #include #include @@ -39,8 +40,7 @@ static bool archive_ordinary_file (const char *file_name, int file_fd, int archive_fd, bool *write_error); static bool archive_directory (char file_name[], size_t file_name_size, int file_fd, int archive_fd, bool *write_error); -static bool write_header (const char *file_name, - char type_flag, int size, unsigned mode, +static bool write_header (const char *file_name, enum ustar_type, int size, int archive_fd, bool *write_error); static bool do_write (int fd, const char *buffer, int size, bool *write_error); @@ -128,7 +128,8 @@ archive_ordinary_file (const char *file_name, int file_fd, bool success = true; int file_size = filesize (file_fd); - if (!write_header (file_name, '0', file_size, 0644, archive_fd, write_error)) + if (!write_header (file_name, USTAR_REGULAR, file_size, + archive_fd, write_error)) return false; while (file_size > 0) @@ -169,7 +170,7 @@ archive_directory (char file_name[], size_t file_name_size, int file_fd, return false; } - if (!write_header (file_name, '5', 0, 0755, archive_fd, write_error)) + if (!write_header (file_name, USTAR_DIRECTORY, 0, archive_fd, write_error)) return false; file_name[dir_len] = '/'; @@ -182,55 +183,12 @@ archive_directory (char file_name[], size_t file_name_size, int file_fd, } static bool -write_header (const char *file_name, - char type_flag, int size, unsigned mode, +write_header (const char *file_name, enum ustar_type type, int size, int archive_fd, bool *write_error) { static char header[512]; - unsigned chksum; - size_t i; - - memset (header, 0, sizeof header); - - /* Drop confusing and possibly dangerous prefixes from - FILE_NAME. */ - while (*file_name == '/' - || !memcmp (file_name, "./", 2) - || !memcmp (file_name, "../", 3)) - file_name = strchr (file_name, '/') + 1; - if (*file_name == '\0') - { - /* Dropped *everything* from FILE_NAME. - Should only be possible for a directory. */ - ASSERT (type_flag == '5'); - return true; - } - else if (strlen (file_name) > 99) - { - printf ("%s: file name too long\n", file_name); - return false; - } - - /* Fill in header except for final checksum. */ - strlcpy (header, file_name, 100); /* name */ - snprintf (header + 100, 8, "%07o", mode); /* mode */ - strlcpy (header + 108, "0000000", 8); /* uid */ - strlcpy (header + 116, "0000000", 8); /* gid */ - snprintf (header + 124, 12, "%011o", size); /* size */ - snprintf (header + 136, 12, "%011o", 1136102400); /* mtime (2006-01-01) */ - memset (header + 148, ' ', 8); /* chksum */ - header[156] = type_flag; /* typeflag */ - strlcpy (header + 257, "ustar", 6); /* magic */ - strlcpy (header + 263, "00", 3); /* version */ - - /* Compute and fill in final checksum. */ - chksum = 0; - for (i = 0; i < 512; i++) - chksum += (uint8_t) header[i]; - snprintf (header + 148, 8, "%07o", chksum); - - /* Write header. */ - return do_write (archive_fd, header, 512, write_error); + return (ustar_make_header (file_name, type, size, header) + && do_write (archive_fd, header, 512, write_error)); } static bool diff --git a/src/tests/tests.pm b/src/tests/tests.pm index 29e0707..4599cb9 100644 --- a/src/tests/tests.pm +++ b/src/tests/tests.pm @@ -561,12 +561,15 @@ sub read_tar { $size = 0 if $typeflag eq '5'; # Store content. + $name =~ s%^(/|\./|\.\./)*%%; # Strip leading "/", "./", "../". + $name = '' if $name eq '.' || $name eq '..'; if (exists $content{$name}) { fail "$archive: contains multiple entries for $name\n"; } if ($typeflag eq '5') { - $content{$name} = 'directory'; + $content{$name} = 'directory' if $name ne ''; } else { + fail "$archive: contains file with empty name\n" if $name eq ''; my ($position) = sysseek (ARCHIVE, 0, SEEK_CUR); $content{$name} = [$archive, $position, $size]; sysseek (ARCHIVE, int (($size + 511) / 512) * 512, SEEK_CUR); diff --git a/src/tests/userprog/Make.tests b/src/tests/userprog/Make.tests index 8d64c05..caadd90 100644 --- a/src/tests/userprog/Make.tests +++ b/src/tests/userprog/Make.tests @@ -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 \ diff --git a/src/threads/Make.vars b/src/threads/Make.vars index dc45c2a..310c240 100644 --- a/src/threads/Make.vars +++ b/src/threads/Make.vars @@ -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 diff --git a/src/threads/init.c b/src/threads/init.c index 5893ce9..66bc867 100644 --- a/src/threads/init.c +++ b/src/threads/init.c @@ -1,18 +1,21 @@ #include "threads/init.h" #include #include +#include #include #include #include -#include #include #include #include #include "devices/kbd.h" #include "devices/input.h" +#include "devices/pci.h" +#include "devices/usb.h" #include "devices/serial.h" #include "devices/timer.h" #include "devices/vga.h" +#include "devices/rtc.h" #include "threads/interrupt.h" #include "threads/io.h" #include "threads/loader.h" @@ -30,35 +33,47 @@ #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 ram_pages; - /* Page directory with kernel mappings only. */ uint32_t *base_page_dir; +bool base_page_dir_initialized = 0; #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 /* -q: Power off after kernel tasks complete? */ bool power_off_when_done; -static void ram_init (void); +static void bss_init (void); static void paging_init (void); +static void pci_zone_init (void); static char **read_command_line (void); static char **parse_options (char **argv); static void run_actions (char **argv); static void usage (void); -static void print_stats (void); +#ifdef FILESYS +static void locate_block_devices (void); +static void locate_block_device (enum block_type, const char *name); +#endif +static void print_stats (void); int main (void) NO_RETURN; @@ -67,9 +82,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 (); @@ -81,7 +96,8 @@ main (void) console_init (); /* Greet user. */ - printf ("Pintos booting with %'zu kB RAM...\n", ram_pages * PGSIZE / 1024); + printf ("Pintos booting with %'"PRIu32" kB RAM...\n", + ram_pages * PGSIZE / 1024); /* Initialize memory system. */ palloc_init (); @@ -98,6 +114,7 @@ main (void) intr_init (); timer_init (); kbd_init (); + pci_init (); input_init (); #ifdef USERPROG exception_init (); @@ -108,10 +125,13 @@ main (void) thread_start (); serial_init_queue (); timer_calibrate (); + usb_init (); #ifdef FILESYS /* Initialize file system. */ - disk_init (); + usb_storage_init (); + ide_init (); + locate_block_devices (); filesys_init (format_filesys); #endif @@ -126,32 +146,23 @@ main (void) thread_exit (); } -/* 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. */ - 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 base_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) { @@ -172,18 +183,39 @@ paging_init (void) if (pd[pde_idx] == 0) { pt = palloc_get_page (PAL_ASSERT | PAL_ZERO); - pd[pde_idx] = pde_create (pt); + pd[pde_idx] = pde_create_kernel (pt); } pt[pte_idx] = pte_create_kernel (vaddr, !in_kernel_text); } + pci_zone_init (); + /* Store the physical address of the page directory into CR3 aka PDBR (page directory base register). This activates our new page tables immediately. See [IA32-v2a] "MOV--Move to/from Control Registers" and [IA32-v3a] 3.7.5 "Base Address of the Page Directory". */ asm volatile ("movl %0, %%cr3" : : "r" (vtop (base_page_dir))); + + base_page_dir_initialized = 1; +} + +/* initialize PCI zone at PCI_ADDR_ZONE_BEGIN - PCI_ADDR_ZONE_END*/ +static void +pci_zone_init (void) +{ + int i; + for (i = 0; i < PCI_ADDR_ZONE_PDES; i++) + { + size_t pde_idx = pd_no ((void *) PCI_ADDR_ZONE_BEGIN) + i; + uint32_t pde; + void *pt; + + pt = palloc_get_page (PAL_ASSERT | PAL_ZERO); + pde = pde_create_kernel (pt); + base_page_dir[pde_idx] = pde; + } } /* Breaks the kernel command line into words and returns them as @@ -239,6 +271,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)); @@ -251,6 +291,16 @@ parse_options (char **argv) else PANIC ("unknown option `%s' (use -h for help)", name); } + + /* Initialize the random number generator based on the system + time. This has no effect if an "-rs" option was specified. + + When running under Bochs, this is not enough by itself to + get a good seed value, because the pintos script sets the + initial time to a predictable value, not to the local time, + for reproducibility. To fix this, give the "-r" option to + the pintos script to request real-time execution. */ + random_init (rtc_get_time ()); return argv; } @@ -291,8 +341,8 @@ run_actions (char **argv) {"ls", 1, fsutil_ls}, {"cat", 2, fsutil_cat}, {"rm", 2, fsutil_rm}, - {"put", 2, fsutil_put}, - {"get", 2, fsutil_get}, + {"extract", 1, fsutil_extract}, + {"append", 2, fsutil_append}, #endif {NULL, 0, NULL}, }; @@ -340,13 +390,20 @@ 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" - " put FILE Put FILE into file system from scratch disk.\n" + " extract Untar from scratch disk into file system.\n" " get FILE Get FILE from file system into scratch disk.\n" #endif "\nOptions:\n" " -h Print this help message and power off.\n" " -q Power off VM after actions or on panic.\n" +#ifdef FILESYS " -f Format file system disk 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 @@ -356,6 +413,47 @@ usage (void) power_off (); } +#ifdef FILESYS +/* Figure out what disks 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 /* Powers down the machine we're running on, as long as we're running on Bochs or QEMU. */ @@ -388,7 +486,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 (); diff --git a/src/threads/init.h b/src/threads/init.h index a6fec05..863d1de 100644 --- a/src/threads/init.h +++ b/src/threads/init.h @@ -6,9 +6,6 @@ #include #include -/* Physical memory size, in 4 kB pages. */ -extern size_t ram_pages; - /* Page directory with kernel mappings only. */ extern uint32_t *base_page_dir; diff --git a/src/threads/interrupt.c b/src/threads/interrupt.c index 075962f..27f4708 100644 --- a/src/threads/interrupt.c +++ b/src/threads/interrupt.c @@ -10,6 +10,16 @@ #include "threads/vaddr.h" #include "devices/timer.h" +/* Programmable Interrupt Controller (PIC) registers. + A PC has two PICs, called the master and slave PICs, with the + slave attached ("cascaded") to the master IRQ line 2. */ +#define PIC0_CTRL 0x20 /* Master PIC control register address. */ +#define PIC0_DATA 0x21 /* Master PIC data register address. */ +#define PIC1_CTRL 0xa0 /* Slave PIC control register address. */ +#define PIC1_DATA 0xa1 /* Slave PIC data register address. */ +#define IRQ_CASCADE0 2 +#define IRQ_CASCADE1 9 + /* Number of x86 interrupts. */ #define INTR_CNT 256 @@ -25,6 +35,9 @@ static intr_handler_func *intr_handlers[INTR_CNT]; /* Names for each interrupt, for debugging purposes. */ static const char *intr_names[INTR_CNT]; +/* cached values for PIC */ +static uint8_t pic_mask[2]; + /* External interrupts are those generated by devices outside the CPU, such as the timer. External interrupts run with interrupts turned off, so they never nest, nor are they ever @@ -214,42 +227,37 @@ intr_yield_on_return (void) /* 8259A Programmable Interrupt Controller. */ -/* Every PC has two 8259A Programmable Interrupt Controller (PIC) - chips. One is a "master" accessible at ports 0x20 and 0x21. - The other is a "slave" cascaded onto the master's IRQ 2 line - and accessible at ports 0xa0 and 0xa1. Accesses to port 0x20 - set the A0 line to 0 and accesses to 0x21 set the A1 line to - 1. The situation is similar for the slave PIC. +/* Initializes the PICs. Refer to [8259A] for details. By default, interrupts 0...15 delivered by the PICs will go to - interrupt vectors 0...15. Unfortunately, those vectors are - also used for CPU traps and exceptions. We reprogram the PICs - so that interrupts 0...15 are delivered to interrupt vectors - 32...47 (0x20...0x2f) instead. */ - -/* Initializes the PICs. Refer to [8259A] for details. */ + interrupt vectors 0...15. Those vectors are also used for CPU + traps and exceptions, so we reprogram the PICs so that + interrupts 0...15 are delivered to interrupt vectors 32...47 + (0x20...0x2f) instead. */ static void pic_init (void) { /* Mask all interrupts on both PICs. */ - outb (0x21, 0xff); - outb (0xa1, 0xff); + outb (PIC0_DATA, 0xff); + outb (PIC1_DATA, 0xff); /* Initialize master. */ - outb (0x20, 0x11); /* ICW1: single mode, edge triggered, expect ICW4. */ - outb (0x21, 0x20); /* ICW2: line IR0...7 -> irq 0x20...0x27. */ - outb (0x21, 0x04); /* ICW3: slave PIC on line IR2. */ - outb (0x21, 0x01); /* ICW4: 8086 mode, normal EOI, non-buffered. */ + outb (PIC0_CTRL, 0x11); /* ICW1: single mode, edge triggered, expect ICW4. */ + outb (PIC0_DATA, 0x20); /* ICW2: line IR0...7 -> irq 0x20...0x27. */ + outb (PIC0_DATA, 0x04); /* ICW3: slave PIC on line IR2. */ + outb (PIC0_DATA, 0x01); /* ICW4: 8086 mode, normal EOI, non-buffered. */ /* Initialize slave. */ - outb (0xa0, 0x11); /* ICW1: single mode, edge triggered, expect ICW4. */ - outb (0xa1, 0x28); /* ICW2: line IR0...7 -> irq 0x28...0x2f. */ - outb (0xa1, 0x02); /* ICW3: slave ID is 2. */ - outb (0xa1, 0x01); /* ICW4: 8086 mode, normal EOI, non-buffered. */ + outb (PIC1_CTRL, 0x11); /* ICW1: single mode, edge triggered, expect ICW4. */ + outb (PIC1_DATA, 0x28); /* ICW2: line IR0...7 -> irq 0x28...0x2f. */ + outb (PIC1_DATA, 0x02); /* ICW3: slave ID is 2. */ + outb (PIC1_DATA, 0x01); /* ICW4: 8086 mode, normal EOI, non-buffered. */ /* Unmask all interrupts. */ - outb (0x21, 0x00); - outb (0xa1, 0x00); + outb (PIC0_DATA, 0x00); + outb (PIC1_DATA, 0x00); + pic_mask[0] = 0; + pic_mask[1] = 0; } /* Sends an end-of-interrupt signal to the PIC for the given IRQ. @@ -417,3 +425,44 @@ intr_name (uint8_t vec) { return intr_names[vec]; } + +/** masks a given IRQ */ +void intr_irq_mask(int irq) +{ + if(irq < 8){ + pic_mask[0] |= 1 << irq; + outb (PIC0_DATA, pic_mask[0]); + }else{ + pic_mask[1] |= 1 << (irq - 8); + outb (PIC1_DATA, pic_mask[1]); + } +} + +/** unmasks a given IRQ */ +void intr_irq_unmask(int irq) +{ + if(irq >= 8){ + /* enable cascade if not enabled for pic2 */ + if(pic_mask[1] & (1 << (IRQ_CASCADE1 - 8))) + pic_mask[1] &= ~(1 << (IRQ_CASCADE1 - 8)); + + pic_mask[1] &= ~(1 << (irq - 8)); + outb(PIC1_DATA, pic_mask[1]); + + /* enable cascade if not enabled for pic1 */ + if(pic_mask[0] & (1 << IRQ_CASCADE0)) + irq = IRQ_CASCADE0; + } + + if(irq < 8){ + pic_mask[0] &= ~(1 << irq); + outb (PIC0_DATA, pic_mask[0]); + } + +} + +/* return whether an interrupt vector is registered */ +bool intr_is_registered(uint8_t vec_no) +{ + return (intr_handlers[vec_no] != NULL); +} diff --git a/src/threads/interrupt.h b/src/threads/interrupt.h index d43e06d..79d63e7 100644 --- a/src/threads/interrupt.h +++ b/src/threads/interrupt.h @@ -67,4 +67,9 @@ void intr_yield_on_return (void); void intr_dump_frame (const struct intr_frame *); const char *intr_name (uint8_t vec); +void intr_irq_mask(int irq); +void intr_irq_unmask(int irq); + +bool intr_is_registered ( uint8_t vec ); + #endif /* threads/interrupt.h */ diff --git a/src/threads/io.h b/src/threads/io.h index b493299..30d52da 100644 --- a/src/threads/io.h +++ b/src/threads/io.h @@ -1,43 +1,3 @@ -/* 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. - */ - #ifndef THREADS_IO_H #define THREADS_IO_H @@ -50,7 +10,7 @@ inb (uint16_t port) { /* See [IA32-v2a] "IN". */ uint8_t data; - asm volatile ("inb %w1,%0" : "=a" (data) : "d" (port)); + asm volatile ("inb %w1, %b0" : "=a" (data) : "Nd" (port)); return data; } @@ -60,10 +20,7 @@ static inline void insb (uint16_t port, void *addr, size_t cnt) { /* See [IA32-v2a] "INS". */ - asm volatile ("cld; repne; insb" - : "=D" (addr), "=c" (cnt) - : "d" (port), "0" (addr), "1" (cnt) - : "memory", "cc"); + asm volatile ("rep insb" : "+D" (addr), "+c" (cnt) : "d" (port) : "memory"); } /* Reads and returns 16 bits from PORT. */ @@ -72,7 +29,7 @@ inw (uint16_t port) { uint16_t data; /* See [IA32-v2a] "IN". */ - asm volatile ("inw %w1,%0" : "=a" (data) : "d" (port)); + asm volatile ("inw %w1, %w0" : "=a" (data) : "Nd" (port)); return data; } @@ -82,10 +39,7 @@ static inline void insw (uint16_t port, void *addr, size_t cnt) { /* See [IA32-v2a] "INS". */ - asm volatile ("cld; repne; insw" - : "=D" (addr), "=c" (cnt) - : "d" (port), "0" (addr), "1" (cnt) - : "memory", "cc"); + asm volatile ("rep insw" : "+D" (addr), "+c" (cnt) : "d" (port) : "memory"); } /* Reads and returns 32 bits from PORT. */ @@ -94,7 +48,7 @@ inl (uint16_t port) { /* See [IA32-v2a] "IN". */ uint32_t data; - asm volatile ("inl %w1,%0" : "=a" (data) : "d" (port)); + asm volatile ("inl %w1, %0" : "=a" (data) : "Nd" (port)); return data; } @@ -104,10 +58,7 @@ static inline void insl (uint16_t port, void *addr, size_t cnt) { /* See [IA32-v2a] "INS". */ - asm volatile ("cld; repne; insl" - : "=D" (addr), "=c" (cnt) - : "d" (port), "0" (addr), "1" (cnt) - : "memory", "cc"); + asm volatile ("rep insl" : "+D" (addr), "+c" (cnt) : "d" (port) : "memory"); } /* Writes byte DATA to PORT. */ @@ -115,7 +66,7 @@ static inline void outb (uint16_t port, uint8_t data) { /* See [IA32-v2b] "OUT". */ - asm volatile ("outb %0,%w1" : : "a" (data), "d" (port)); + asm volatile ("outb %b0, %w1" : : "a" (data), "Nd" (port)); } /* Writes to PORT each byte of data in the CNT-byte buffer @@ -124,10 +75,7 @@ static inline void outsb (uint16_t port, const void *addr, size_t cnt) { /* See [IA32-v2b] "OUTS". */ - asm volatile ("cld; repne; outsb" - : "=S" (addr), "=c" (cnt) - : "d" (port), "0" (addr), "1" (cnt) - : "cc"); + asm volatile ("rep outsb" : "+S" (addr), "+c" (cnt) : "d" (port)); } /* Writes the 16-bit DATA to PORT. */ @@ -135,7 +83,7 @@ static inline void outw (uint16_t port, uint16_t data) { /* See [IA32-v2b] "OUT". */ - asm volatile ("outw %0,%w1" : : "a" (data), "d" (port)); + asm volatile ("outw %w0, %w1" : : "a" (data), "Nd" (port)); } /* Writes to PORT each 16-bit unit (halfword) of data in the @@ -144,10 +92,7 @@ static inline void outsw (uint16_t port, const void *addr, size_t cnt) { /* See [IA32-v2b] "OUTS". */ - asm volatile ("cld; repne; outsw" - : "=S" (addr), "=c" (cnt) - : "d" (port), "0" (addr), "1" (cnt) - : "cc"); + asm volatile ("rep outsw" : "+S" (addr), "+c" (cnt) : "d" (port)); } /* Writes the 32-bit DATA to PORT. */ @@ -155,7 +100,7 @@ static inline void outl (uint16_t port, uint32_t data) { /* See [IA32-v2b] "OUT". */ - asm volatile ("outl %0,%w1" : : "a" (data), "d" (port)); + asm volatile ("outl %0, %w1" : : "a" (data), "Nd" (port)); } /* Writes to PORT each 32-bit unit (word) of data in the CNT-word @@ -164,10 +109,7 @@ static inline void outsl (uint16_t port, const void *addr, size_t cnt) { /* See [IA32-v2b] "OUTS". */ - asm volatile ("cld; repne; outsl" - : "=S" (addr), "=c" (cnt) - : "d" (port), "0" (addr), "1" (cnt) - : "cc"); + asm volatile ("rep outsl" : "+S" (addr), "+c" (cnt) : "d" (port)); } #endif /* threads/io.h */ diff --git a/src/threads/kernel.lds.S b/src/threads/kernel.lds.S index 6154d08..19082d5 100644 --- a/src/threads/kernel.lds.S +++ b/src/threads/kernel.lds.S @@ -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.") } diff --git a/src/threads/loader.S b/src/threads/loader.S index b7842d3..f5c998d 100644 --- a/src/threads/loader.S +++ b/src/threads/loader.S @@ -1,349 +1,263 @@ -/* 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 $0xe3, %al # 9600 bps, N-8-1. + # AH is already 0 (Initialize Port). + int $0x14 # Destroys AX. + + call puts + .string "PiLo" + +#### 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. +2: int $0x14 # Destroys AH. + test $0x80, %ah # Output timed out? + jz 3f + movw $0x9090, 2b + +3: + 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 %ax, %ax + push %ax # LBA sector number [48:63] + push %ax # LBA sector number [32:47] + 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 diff --git a/src/threads/loader.h b/src/threads/loader.h index 5bd9813..d89c45f 100644 --- a/src/threads/loader.h +++ b/src/threads/loader.h @@ -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. */ @@ -35,4 +30,11 @@ #define SEL_KCSEG 0x08 /* Kernel code selector. */ #define SEL_KDSEG 0x10 /* Kernel data selector. */ +#ifndef __ASSEMBLER__ +#include + +/* Amount of physical memory, in 4 kB pages. */ +extern uint32_t ram_pages; +#endif + #endif /* threads/loader.h */ diff --git a/src/threads/palloc.c b/src/threads/palloc.c index eab41e4..ddbfeda 100644 --- a/src/threads/palloc.c +++ b/src/threads/palloc.c @@ -7,7 +7,6 @@ #include #include #include -#include "threads/init.h" #include "threads/loader.h" #include "threads/synch.h" #include "threads/vaddr.h" @@ -43,17 +42,14 @@ size_t user_page_limit = SIZE_MAX; static void init_pool (struct pool *, void *base, size_t page_cnt, const char *name); static bool page_from_pool (const struct pool *, void *page); +static void page_set_cache(void* page, bool enable_cache); /* Initializes the page allocator. */ void palloc_init (void) { - /* 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 (ram_pages * PGSIZE); size_t free_pages = (free_end - free_start) / PGSIZE; size_t user_pages = free_pages / 2; diff --git a/src/threads/palloc.h b/src/threads/palloc.h index 2d41cf6..aed1525 100644 --- a/src/threads/palloc.h +++ b/src/threads/palloc.h @@ -6,14 +6,17 @@ /* How to allocate pages. */ enum palloc_flags { - PAL_ASSERT = 001, /* Panic on failure. */ - PAL_ZERO = 002, /* Zero page contents. */ - PAL_USER = 004 /* User page. */ + PAL_ASSERT = 0x1, /* Panic on failure. */ + PAL_ZERO = 0x2, /* Zero page contents. */ + PAL_USER = 0x4, /* User page. */ + PAL_NOCACHE = 0x8 /* Disable memory caching for page. */ }; /* Maximum number of pages to put in user pool. */ extern size_t user_page_limit; +extern void* zero_page; + void palloc_init (void); void *palloc_get_page (enum palloc_flags); void *palloc_get_multiple (enum palloc_flags, size_t page_cnt); diff --git a/src/threads/pte.h b/src/threads/pte.h index 1660727..9133c45 100644 --- a/src/threads/pte.h +++ b/src/threads/pte.h @@ -28,6 +28,14 @@ #define PDBITS 10 /* Number of page dir bits. */ #define PDMASK BITMASK(PDSHIFT, PDBITS) /* Page directory bits (22:31). */ +/* memory dedicated to PCI - make sure this is 4MB aligned */ +#define PCI_ADDR_ZONE_BEGIN 0xe0000000 +#define PCI_ADDR_ZONE_END 0xe0800000 +#define PCI_ADDR_ZONE_PDES 2 +#define PCI_ADDR_ZONE_PAGES (PCI_ADDR_ZONE_END-PCI_ADDR_ZONE_BEGIN)/PGSIZE + + + /* Obtains page table index from a virtual address. */ static inline unsigned pt_no (const void *va) { return ((uintptr_t) va & PTMASK) >> PTSHIFT; @@ -64,15 +72,24 @@ static inline uintptr_t pd_no (const void *va) { #define PTE_P 0x1 /* 1=present, 0=not present. */ #define PTE_W 0x2 /* 1=read/write, 0=read-only. */ #define PTE_U 0x4 /* 1=user/kernel, 0=kernel only. */ +#define PTE_WT (1 << 3) /* 1=write-through, 0=write-back */ +#define PTE_CD (1 << 4) /* 1=cache disabled, 0=cache enabled. */ #define PTE_A 0x20 /* 1=accessed, 0=not acccessed. */ #define PTE_D 0x40 /* 1=dirty, 0=not dirty (PTEs only). */ +#define PTE_G (1 << 8) /* 1=global page, do not flush */ /* Returns a PDE that points to page table PT. */ -static inline uint32_t pde_create (uint32_t *pt) { +static inline uint32_t pde_create_user (uint32_t *pt) { ASSERT (pg_ofs (pt) == 0); return vtop (pt) | PTE_U | PTE_P | PTE_W; } +/* Returns a PDE that points to page table PT. */ +static inline uint32_t pde_create_kernel (uint32_t *pt) { + ASSERT (pg_ofs (pt) == 0); + return vtop (pt) | PTE_P | PTE_W | PTE_G; +} + /* Returns a pointer to the page table that page directory entry PDE, which must "present", points to. */ static inline uint32_t *pde_get_pt (uint32_t pde) { @@ -86,7 +103,7 @@ static inline uint32_t *pde_get_pt (uint32_t pde) { The page will be usable only by ring 0 code (the kernel). */ static inline uint32_t pte_create_kernel (void *page, bool writable) { ASSERT (pg_ofs (page) == 0); - return vtop (page) | PTE_P | (writable ? PTE_W : 0); + return vtop (page) | PTE_P | (writable ? PTE_W : 0) | PTE_G; } /* Returns a PTE that points to PAGE. @@ -94,7 +111,8 @@ static inline uint32_t pte_create_kernel (void *page, bool writable) { If WRITABLE is true then it will be writable as well. The page will be usable by both user and kernel code. */ static inline uint32_t pte_create_user (void *page, bool writable) { - return pte_create_kernel (page, writable) | PTE_U; + ASSERT (pg_ofs (page) == 0); + return vtop (page) | PTE_P | (writable ? PTE_W : 0) | PTE_U; } /* Returns a pointer to the page that page table entry PTE points diff --git a/src/threads/start.S b/src/threads/start.S index 5a495c4..fe2200b 100644 --- a/src/threads/start.S +++ b/src/threads/start.S @@ -1,16 +1,203 @@ -#### 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 - # Call main. -start: call main +.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, 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 + +# 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 + +#### 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 ram_pages +ram_pages: + .long 0 + diff --git a/src/userprog/Make.vars b/src/userprog/Make.vars index 9db3cfa..e4dbb08 100644 --- a/src/userprog/Make.vars +++ b/src/userprog/Make.vars @@ -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/userprog/pagedir.c b/src/userprog/pagedir.c index 30bdfe2..68c0d19 100644 --- a/src/userprog/pagedir.c +++ b/src/userprog/pagedir.c @@ -74,7 +74,7 @@ lookup_page (uint32_t *pd, const void *vaddr, bool create) if (pt == NULL) return NULL; - *pde = pde_create (pt); + *pde = pde_create_user (pt); } else return NULL; diff --git a/src/utils/Pintos.pm b/src/utils/Pintos.pm new file mode 100644 index 0000000..6bee1e1 --- /dev/null +++ b/src/utils/Pintos.pm @@ -0,0 +1,494 @@ +# 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}}; + $geometry{H} = 16 if !defined $geometry{H}; + $geometry{S} = 63 if !defined $geometry{S}; + + 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; + } + $table = "\0" x 16 . $table while length ($table) < 64; + 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; diff --git a/src/utils/pintos b/src/utils/pintos index 9413205..eff91be 100755 --- a/src/utils/pintos +++ b/src/utils/pintos @@ -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,18 @@ 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. +our ($fake_usb) = 0; # Use fake USB disks instead of IDE? parse_command_line (); -find_disks (); prepare_scratch_disk (); -prepare_arguments (); +find_disks (); run_vm (); finish_scratch_disk (); @@ -39,7 +45,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 = (); @@ -50,6 +56,7 @@ sub parse_command_line { "bochs" => sub { set_sim ("bochs") }, "qemu" => sub { set_sim ("qemu") }, "player" => sub { set_sim ("player") }, + "hardware" => sub { set_sim ("hardware") }, "debug=s" => sub { set_debug ($_[1]) }, "no-debug" => sub { set_debug ("none") }, @@ -73,27 +80,51 @@ 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, + "fake-usb" => \$fake_usb, + + "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; + + if ($sim eq 'hardware') { + $make_disk = "pintos.dsk" if !defined $make_disk; + $tmp_disk = 0; + $align = 'full' if !defined $align; + } + + $fake_usb = 0, print "warning: ignoring --fake-usb with $sim simulator\n" + if $fake_usb && $sim ne 'qemu'; 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). @@ -110,6 +141,7 @@ Simulator selection: --bochs (default) Use Bochs as simulator --qemu Use QEMU as simulator --player Use VMware Player as simulator + --hardware Don't run simulator; create images for real hardware Debugger selection: --no-debug (default) No debugger --monitor Debug with simulator's monitor @@ -128,15 +160,28 @@ 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 + --fake-usb Use USB disks, not IDE, except for boot (qemu only) Other options: -h, --help Display this help message. EOF @@ -201,106 +246,248 @@ 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}; + } +} # 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"; + } + + # Add "get" and "put" files to kernel args. + 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; } # Prepare the scratch disk for gets and puts. sub prepare_scratch_disk { - # Copy the files to put onto the scratch disk. - put_scratch_file ($_->[0]) foreach @puts; + return if !@gets && !@puts; + + my ($p) = $parts{SCRATCH}; - # Make sure the scratch disk is big enough to get big files. - extend_disk ($disks{SCRATCH}, @gets * 1024 * 1024) if @gets; + # Create temporary partition and write the files to put to it. + my ($part_handle, $part_fn) = tempfile (UNLINK => 1, SUFFIX => '.part'); + if (@puts) { + # Write ustar header and data for each file. + put_scratch_file ($_->[0], + defined $_->[1] ? $_->[1] : $_->[0], + $part_handle, $part_fn) + foreach @puts; + + # Write end-of-archive marker. + print $part_handle "\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); + } } # 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 || $sim eq 'hardware'; + + # 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 stop reading files. my ($ok) = 1; + my ($part_end) = ($p->{START} + $p->{SECTORS}) * 512; foreach my $get (@gets) { my ($name) = defined ($get->[1]) ? $get->[1] : $get->[0]; - $ok &&= get_scratch_file ($name); + my ($error) = get_scratch_file ($name, $part_handle, $part_fn); + if ($error) { + print STDERR "$part_fn: getting $name failed ($error)\n"; + $ok = 0; + } + if (sysseek ($part_handle, 0, SEEK_CUR) > $part_end) { + $ok = 0; + print STDERR "$part_fn: scratch data overflows partition\n"; + } if (!$ok) { die "$name: unlink: $!\n" if !unlink ($name) && !$!{ENOENT}; + last; } } } -# put_scratch_file($file). +# mk_ustar_field($number, $size) # -# Copies $file into the scratch disk. +# Returns $number in a $size-byte numeric field in the format used by +# the standard ustar archive header. +sub mk_ustar_field { + my ($number, $size) = @_; + my ($len) = $size - 1; + my ($out) = sprintf ("%0${len}o", $number) . "\0"; + die "$number: too large for $size-byte octal ustar field\n" + if length ($out) != $size; + return $out; +} + +# calc_ustar_chksum($s) +# +# Calculates and returns the ustar checksum of 512-byte ustar archive +# header $s. +sub calc_ustar_chksum { + my ($s) = @_; + die if length ($s) != 512; + substr ($s, 148, 8, ' ' x 8); + return unpack ("%32a*", $s); +} + +# put_scratch_file($src_file_name, $dst_file_name, +# $disk_handle, $disk_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 ($put_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"; - print "Copying $put_file_name into $disk_file_name...\n"; + # ustar format supports up to 100 characters for a file name, and + # even longer names given some common properties, but our code in + # the Pintos kernel only supports at most 99 characters. + die "$dst_file_name: name too long (max 99 characters)\n" + if length ($dst_file_name) > 99; - # Write metadata sector, which consists of a 4-byte signature - # followed by the file size. - stat $put_file_name or die "$put_file_name: stat: $!\n"; + # Compose and write ustar header. + stat $src_file_name or die "$src_file_name: stat: $!\n"; my ($size) = -s _; - my ($metadata) = pack ("a4 V x504", "PUT\0", $size); - write_fully ($disk_handle, $disk_file_name, $metadata); + my ($header) = (pack ("a100", $dst_file_name) # name + . mk_ustar_field (0644, 8) # mode + . mk_ustar_field (0, 8) # uid + . mk_ustar_field (0, 8) # gid + . mk_ustar_field ($size, 12) # size + . mk_ustar_field (1136102400, 12) # mtime + . (' ' x 8) # chksum + . '0' # typeflag + . ("\0" x 100) # linkname + . "ustar\0" # magic + . "00" # version + . "root" . ("\0" x 28) # uname + . "root" . ("\0" x 28) # gname + . "\0" x 8 # devmajor + . "\0" x 8 # devminor + . ("\0" x 155)) # prefix + . "\0" x 12; # pad to 512 bytes + substr ($header, 148, 8) = mk_ustar_field (calc_ustar_chksum ($header), 8); + write_fully ($disk_handle, $disk_file_name, $header); # Copy file data. my ($put_handle); - sysopen ($put_handle, $put_file_name, O_RDONLY) - or die "$put_file_name: open: $!\n"; - copy_file ($put_handle, $put_file_name, $disk_handle, $disk_file_name, + sysopen ($put_handle, $src_file_name, O_RDONLY) + or die "$src_file_name: open: $!\n"; + copy_file ($put_handle, $src_file_name, $disk_handle, $disk_file_name, $size); + die "$src_file_name: changed size while being read\n" + if $size != -s $put_handle; close ($put_handle); # Round up disk data to beginning of next sector. @@ -308,23 +495,37 @@ 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"; - # Read metadata sector, which has a 4-byte signature followed by - # the file size. - my ($metadata) = read_fully ($disk_handle, $disk_file_name, 512); - my ($signature, $size) = unpack ("a4 V", $metadata); - (print STDERR "bad signature on scratch disk--did Pintos run fail?\n"), - return 0 - if $signature ne "GET\0"; + # Read ustar header sector. + my ($header) = read_fully ($disk_handle, $disk_file_name, 512); + return "scratch disk tar archive ends unexpectedly" + if $header eq ("\0" x 512); + + # Verify magic numbers. + return "corrupt ustar signature" if substr ($header, 257, 6) ne "ustar\0"; + return "invalid ustar version" if substr ($header, 263, 2) ne '00'; + + # Verify checksum. + my ($chksum) = oct (unpack ("Z*", substr ($header, 148, 8))); + my ($correct_chksum) = calc_ustar_chksum ($header); + return "checksum mismatch" if $chksum != $correct_chksum; + + # Get type. + my ($typeflag) = substr ($header, 156, 1); + return "not a regular file" if $typeflag ne '0' && $typeflag ne "\0"; + + # Get size. + my ($size) = oct (unpack ("Z*", substr ($header, 124, 12))); + return "bad size $size\n" if $size < 0; # Copy file data. my ($get_handle); @@ -338,36 +539,7 @@ sub get_scratch_file { read_fully ($disk_handle, $disk_file_name, 512 - $size % 512) if $size % 512; - return 1; -} - -# 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, 'put', defined $_->[1] ? $_->[1] : $_->[0]) foreach @puts; - push (@args, @kernel_args); - push (@args, 'get', $_->[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"; + return 0; } # Running simulators. @@ -380,6 +552,9 @@ sub run_vm { run_qemu (); } elsif ($sim eq 'player') { run_player (); + } elsif ($sim eq 'hardware') { + print "Pintos disk image written to $make_disk.\n"; + print "Copy this image to a physical disk for hardware boot.\n"; } else { die "unknown simulator `$sim'\n"; } @@ -400,7 +575,7 @@ sub run_bochs { # Write bochsrc.txt configuration file. open (BOCHSRC, ">", "bochsrc.txt") or die "bochsrc.txt: create: $!\n"; print BOCHSRC < 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"; @@ -450,17 +622,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"; } @@ -473,10 +639,16 @@ 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'); + if ($fake_usb) { + push (@cmd, '-hda', $disks[0]) if defined $disks[0]; + push (@cmd, '-usb'); + push (@cmd, '-usbdevice', "disk:$_") foreach @disks[1...$#disks]; + } else { + 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'); @@ -506,8 +678,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"; @@ -517,15 +688,18 @@ config.version = 8 guestOS = "linux" memsize = $mem floppy0.present = FALSE -usb.present = FALSE +usb.present = TRUE sound.present = FALSE gui.exitAtPowerOff = TRUE gui.exitOnCLIHLT = TRUE gui.powerOnAtStartUp = TRUE +usb.analyzer.enable = TRUE +debug = TRUE +loglevel.usb = 20 +loglevel.uhci = 20 +loglevel.pci_uhci = 20 EOF - - print VMX <{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"; @@ -554,7 +727,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 <{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"; @@ -646,61 +782,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; -} # Subprocess utilities. diff --git a/src/utils/pintos-mkdisk b/src/utils/pintos-mkdisk index 662b2e5..77f49fb 100755 --- a/src/utils/pintos-mkdisk +++ b/src/utils/pintos-mkdisk @@ -3,35 +3,132 @@ 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"; # "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 index 0000000..a993799 --- /dev/null +++ b/src/utils/pintos-set-cmdline @@ -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 =~ /PiLo/ 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]); +} diff --git a/src/vm/Make.vars b/src/vm/Make.vars index a5ea6b0..e3c33a7 100644 --- a/src/vm/Make.vars +++ b/src/vm/Make.vars @@ -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