Revise documentation accordingly.
Revise reference solution accordingly.
Change real Return key to produce \r, to match what is received on
serial port.
Update shell example program to expect \r at end of file.
Modify testing makefiles to supply /dev/null as input.
Add squish-pty help program to deal with Bochs,
and modify "pintos" to use it.
displayed in the terminal you used to run @command{pintos}. This is
because Pintos sends all output both to the VGA display and to the first
serial port, and by default the serial port is connected to Bochs's
-@code{stdout}. You can log this output to a file by redirecting at the
+@code{stdin} and @code{stdout}. You can log serial output to a file by
+redirecting at the
command line, e.g.@: @code{pintos run alarm-multiple > logfile}.
The @command{pintos} program offers several options for configuring the
the VM. Finally, you can select how you want VM output to be displayed:
use @option{-v} to turn off the VGA display, @option{-t} to use your
terminal window as the VGA display instead of opening a new window
-(Bochs only), or @option{-s} to suppress the serial output to
-@code{stdout}.
+(Bochs only), or @option{-s} to suppress serial input from @code{stdin}
+and output to @code{stdout}.
The Pintos kernel has commands and options other than @command{run}.
These are not very interesting for now, but you can see a list of them
@func{intr_init} sets up the CPU's @dfn{interrupt descriptor table}
(IDT) to ready it for interrupt handling (@pxref{Interrupt
Infrastructure}), then @func{timer_init} and @func{kbd_init} prepare for
-handling timer interrupts and keyboard interrupts, respectively. In
+handling timer interrupts and keyboard interrupts, respectively.
+@func{input_init} sets up to merge serial and keyboard input into one
+stream. In
projects 2 and later, we also prepare to handle interrupts caused by
user programs using @func{exception_init} and @func{syscall_init}.
@item serial.c
@itemx serial.h
Serial port driver. Again, @func{printf} calls this code for you,
-so you don't need to do so yourself. Feel free to look through it if
-you're curious.
+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 kbd.c
+@itemx kbd.h
+Keyboard driver. Handles keystrokes passing them to the input layer
+(see below).
+
+@item input.c
+@itemx input.h
+Input layer. Queues input characters passed along by the keyboard or
+serial drivers.
+
@item intq.c
@itemx intq.h
Interrupt queue, for managing a circular queue that both kernel
@var{buffer}. Returns the number of bytes actually read (0 at end of
file), or -1 if the file could not be read (due to a condition other
than end of file). Fd 0 reads from the keyboard using
-@func{kbd_getc}. (Keyboard input will not work if you pass the
-@option{-v} option to @command{pintos}.)
+@func{input_getc}.
@end deftypefn
@deftypefn {System Call} int write (int @var{fd}, const void *@var{buffer}, unsigned @var{size})
@code{pid_t}. By default, they're both @code{int}. You can make them
a one-to-one mapping, so that the same values in both identify the
same process, or you can use a more complex mapping. It's up to you.
-
-@item Keyboard input doesn't work.
-
-You are probably passing @option{-v} to @command{pintos}, but
-serial input isn't implemented. Don't use @option{-v} if you
-want to use the shell or otherwise need keyboard input.
@end table
@menu
#include <syscall-nr.h>
+#include "userprog/process.h"
+#include "userprog/pagedir.h"
-+#include "devices/kbd.h"
++#include "devices/input.h"
+#include "filesys/filesys.h"
+#include "filesys/file.h"
+#include "threads/init.h"
+ if (handle == STDIN_FILENO)
+ {
+ for (bytes_read = 0; (size_t) bytes_read < size; bytes_read++)
-+ if (udst >= (uint8_t *) PHYS_BASE || !put_user (udst++, kbd_getc ()))
++ if (udst >= (uint8_t *) PHYS_BASE || !put_user (udst++, input_getc ()))
+ thread_exit ();
+ return bytes_read;
+ }
#include <syscall-nr.h>
+#include "userprog/process.h"
+#include "userprog/pagedir.h"
-+#include "devices/kbd.h"
++#include "devices/input.h"
+#include "filesys/directory.h"
+#include "filesys/filesys.h"
+#include "filesys/file.h"
+
+ for (i = 0; i < read_amt; i++)
+ {
-+ char c = kbd_getc ();
++ char c = input_getc ();
+ if (!page_lock (udst, true))
+ thread_exit ();
+ udst[i] = c;
diff -u src/filesys/directory.c~ src/filesys/directory.c
--- src/filesys/directory.c~
+++ src/filesys/directory.c
-@@ -21,12 +21,36 @@ struct dir_entry
+@@ -1,4 +1,5 @@
+ #include <string.h>
+ #include <list.h>
++#include "filesys/free-map.h"
+ #include "filesys/filesys.h"
+ #include "filesys/inode.h"
+@@ -21,12 +21,39 @@ struct dir_entry
bool in_use; /* In use or free? */
};
-/* Creates a directory with space for ENTRY_CNT entries in the
- given SECTOR. Returns true if successful, false on failure. */
+/* Creates a directory in the given SECTOR.
-+ The directory's parent is in PARENT_SECTOR. */
- bool
++ The directory's parent is in PARENT_SECTOR.
++ Returns inode of created directory if successful,
++ null pointer on faiilure.
++ On failure, SECTOR is released in the free map. */
+-bool
++struct inode *
-dir_create (disk_sector_t sector, size_t entry_cnt)
+dir_create (disk_sector_t sector, disk_sector_t parent_sector)
{
- return inode_create (sector, entry_cnt * sizeof (struct dir_entry));
+ struct inode *inode = inode_create (sector, DIR_INODE);
-+ bool success = inode != NULL;
-+ if (success)
++ if (inode != NULL)
+ {
+ struct dir_entry entries[2];
+
+ strlcpy (entries[1].name, "..", sizeof entries[1].name);
+ entries[1].in_use = true;
+
-+ success = (inode_write_at (inode, entries, sizeof entries, 0)
-+ == sizeof entries);
-+ if (!success)
-+ inode_remove (inode);
++ if (inode_write_at (inode, entries, sizeof entries, 0) != sizeof entries)
++ {
++ inode_remove (inode);
++ inode_close (inode);
++ inode = NULL;
++ }
+ }
-+ inode_close (inode);
-+ return success;
++ return inode;
}
/* Opens and returns the directory for the given INODE, of which
/* Opening and closing directories. */
-bool dir_create (disk_sector_t sector, size_t entry_cnt);
-+bool dir_create (disk_sector_t sector, disk_sector_t parent_sector);
++struct inode *dir_create (disk_sector_t sector, disk_sector_t parent_sector);
struct dir *dir_open (struct inode *);
struct dir *dir_open_root (void);
struct dir *dir_reopen (struct dir *);
diff -u src/filesys/file.c~ src/filesys/file.c
--- src/filesys/file.c~
+++ src/filesys/file.c
+@@ -1,4 +1,5 @@
+ #include "filesys/file.h"
+ #include <debug.h>
++#include "filesys/free-map.h"
+ #include "filesys/inode.h"
+ #include "threads/malloc.h"
@@ -11,6 +11,24 @@ struct file
bool deny_write; /* Has file_deny_write() been called? */
};
+/* Creates a file in the given SECTOR,
-+ initially LENGTH bytes long. */
-+bool
++ initially LENGTH bytes long.
++ Returns inode for the file on success, null pointer on failure.
++ On failure, SECTOR is released in the free map. */
++struct inode *
+file_create (disk_sector_t sector, off_t length)
+{
+ struct inode *inode = inode_create (sector, FILE_INODE);
-+ bool success = inode != NULL;
-+ if (success && length != 0)
++ if (inode != NULL && length > 0
++ && inode_write_at (inode, "", 1, length - 1) != 1)
+ {
-+ ASSERT (length >= 0);
-+ success = inode_write_at (inode, "", 1, length - 1) == 1;
-+ if (!success)
-+ inode_remove (inode);
++ inode_remove (inode);
++ inode_close (inode);
++ inode = NULL;
+ }
-+ inode_close (inode);
-+ return success;
++ return inode;
+}
+
/* Opens a file for the given INODE, of which it takes ownership,
struct inode;
/* Opening and closing files. */
-+bool file_create (disk_sector_t sector, off_t length);
++struct inode *file_create (disk_sector_t sector, off_t length);
struct file *file_open (struct inode *);
struct file *file_reopen (struct file *);
void file_close (struct file *);
}
\f
/* Creates a file named NAME with the given INITIAL_SIZE.
-@@ -44,16 +171,24 @@ filesys_done (void)
+@@ -44,16 +171,32 @@ filesys_done (void)
Fails if a file named NAME already exists,
or if internal memory allocation fails. */
bool
-filesys_create (const char *name, off_t initial_size)
+filesys_create (const char *name, off_t initial_size, enum inode_type type)
{
-+ struct dir *dir;
-+ char base_name[NAME_MAX + 1];
- disk_sector_t inode_sector = 0;
+- disk_sector_t inode_sector = 0;
- struct dir *dir = dir_open_root ();
- bool success = (dir != NULL
- && free_map_allocate (1, &inode_sector)
- && inode_create (inode_sector, initial_size)
- && dir_add (dir, name, inode_sector));
+- if (!success && inode_sector != 0)
+- free_map_release (inode_sector, 1);
++ struct dir *dir;
++ char base_name[NAME_MAX + 1];
++ disk_sector_t inode_sector;
++
+ bool success = (resolve_name_to_entry (name, &dir, base_name)
+ && free_map_allocate (&inode_sector));
+ if (success)
+ {
++ struct inode *inode;
+ if (type == FILE_INODE)
-+ success = file_create (inode_sector, initial_size);
++ inode = file_create (inode_sector, initial_size);
++ else
++ inode = dir_create (inode_sector,
++ inode_get_inumber (dir_get_inode (dir)));
++ if (inode != NULL)
++ {
++ success = dir_add (dir, base_name, inode_sector);
++ if (!success)
++ inode_remove (inode);
++ inode_close (inode);
++ }
+ else
-+ success = dir_create (inode_sector,
-+ inode_get_inumber (dir_get_inode (dir)));
++ success = false;
+ }
-+ success = success && dir_add (dir, base_name, inode_sector);
- if (!success && inode_sector != 0)
-- free_map_release (inode_sector, 1);
-+ free_map_release (inode_sector);
dir_close (dir);
return success;
\f
static void must_succeed_function (int, bool) NO_INLINE;
#define MUST_SUCCEED(EXPR) must_succeed_function (__LINE__, EXPR)
-@@ -155,9 +306,15 @@ static void
+@@ -155,9 +306,18 @@ static void
do_format (void)
{
++ struct inode *inode;
printf ("Formatting file system...");
+
+ /* Set up free map. */
- if (!dir_create (ROOT_DIR_SECTOR, 16))
+
+ /* Set up root directory. */
-+ if (!dir_create (ROOT_DIR_SECTOR, ROOT_DIR_SECTOR))
++ inode = dir_create (ROOT_DIR_SECTOR, ROOT_DIR_SECTOR);
++ if (inode == NULL)
PANIC ("root directory creation failed");
++ inode_close (inode);
+
free_map_close ();
+
file_close (free_map_file);
}
-@@ -72,7 +76,7 @@ void
+@@ -72,5 +76,9 @@ void
free_map_create (void)
{
++ struct inode *inode;
++
/* Create inode. */
- if (!inode_create (FREE_MAP_SECTOR, bitmap_file_size (free_map)))
-+ if (!file_create (FREE_MAP_SECTOR, 0))
++ inode = file_create (FREE_MAP_SECTOR, 0);
++ if (inode == NULL)
PANIC ("free map creation failed");
-
++ inode_close (inode);
+
/* Write bitmap to file. */
Index: src/filesys/free-map.h
diff -u src/filesys/free-map.h~ src/filesys/free-map.h
};
/* Returns the number of sectors to allocate for an inode SIZE
-@@ -35,74 +50,54 @@ struct inode
+@@ -35,74 +50,59 @@ struct inode
disk_sector_t sector; /* Sector number of disk location. */
int open_cnt; /* Number of openers. */
bool removed; /* True if deleted, false otherwise. */
-inode_create (disk_sector_t sector, off_t length)
+/* Initializes an inode of the given TYPE, writes the new inode
+ to sector SECTOR on the file system disk, and returns the
-+ inode thus created. Returns a null pointer if unsuccessful. */
++ inode thus created. Returns a null pointer if unsuccessful,
++ in which case SECTOR is released in the free map. */
+struct inode *
+inode_create (disk_sector_t sector, enum inode_type type)
{
- bool success = false;
+ struct cache_block *block;
+ struct inode_disk *disk_inode;
++ struct inode *inode;
- ASSERT (length >= 0);
+ block = cache_lock (sector, EXCLUSIVE);
- free (disk_inode);
- }
- return success;
-+ return inode_open (sector);
++ inode = inode_open (sector);
++ if (inode == NULL)
++ free_map_release (sector);
++ return inode;
}
/* Reads an inode from SECTOR
#include <syscall-nr.h>
+#include "userprog/process.h"
+#include "userprog/pagedir.h"
-+#include "devices/kbd.h"
++#include "devices/input.h"
+#include "filesys/directory.h"
+#include "filesys/filesys.h"
+#include "filesys/file.h"
+ size_t i;
+
+ for (i = 0; i < read_amt; i++)
-+ udst[i] = kbd_getc ();
++ udst[i] = input_getc ();
+ bytes_read = read_amt;
+ }
+
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/input.c # Serial and keyboard input.
devices_SRC += devices/intq.c # Interrupt queue.
# Library code shared between kernel and user programs.
--- /dev/null
+#include "devices/input.h"
+#include <debug.h>
+#include "devices/intq.h"
+#include "devices/serial.h"
+
+/* Stores keys from the keyboard and serial port. */
+static struct intq buffer;
+
+/* Initializes the input buffer. */
+void
+input_init (void)
+{
+ intq_init (&buffer);
+}
+
+/* Adds a key to the input buffer.
+ Interrupts must be off and the buffer must not be full. */
+void
+input_putc (uint8_t key)
+{
+ ASSERT (intr_get_level () == INTR_OFF);
+ ASSERT (!intq_full (&buffer));
+
+ intq_putc (&buffer, key);
+ serial_notify ();
+}
+
+/* Retrieves a key from the input buffer.
+ If the buffer is empty, waits for a key to be pressed. */
+uint8_t
+input_getc (void)
+{
+ enum intr_level old_level;
+ uint8_t key;
+
+ old_level = intr_disable ();
+ key = intq_getc (&buffer);
+ serial_notify ();
+ intr_set_level (old_level);
+
+ return key;
+}
+
+/* Returns true if the input buffer is full,
+ false otherwise.
+ Interrupts must be off. */
+bool
+input_full (void)
+{
+ ASSERT (intr_get_level () == INTR_OFF);
+ return intq_full (&buffer);
+}
--- /dev/null
+#ifndef DEVICES_INPUT_H
+#define DEVICES_INPUT_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+void input_init (void);
+void input_putc (uint8_t);
+uint8_t input_getc (void);
+bool input_full (void);
+
+#endif /* devices/input.h */
#include <debug.h>
#include <stdio.h>
#include <string.h>
-#include "devices/intq.h"
+#include "devices/input.h"
#include "threads/interrupt.h"
#include "threads/io.h"
True when on, false when off. */
static bool caps_lock;
-/* Keyboard buffer. */
-static struct intq buffer;
-
/* Number of keys pressed. */
static int64_t key_cnt;
void
kbd_init (void)
{
- intq_init (&buffer);
intr_register_ext (0x21, keyboard_interrupt, "8042 Keyboard");
}
-/* Retrieves a key from the keyboard buffer.
- If the buffer is empty, waits for a key to be pressed. */
-uint8_t
-kbd_getc (void)
-{
- enum intr_level old_level;
- uint8_t key;
-
- old_level = intr_disable ();
- key = intq_getc (&buffer);
- intr_set_level (old_level);
-
- return key;
-}
-
/* Prints keyboard statistics. */
void
kbd_print_stats (void)
{0x01, "\033"},
{0x0e, "\b"},
{0x0f, "\tQWERTYUIOP"},
- {0x1c, "\n"},
+ {0x1c, "\r"},
{0x1e, "ASDFGHJKL"},
{0x2c, "ZXCVBNM"},
{0x37, "*"},
c += 0x80;
/* Append to keyboard buffer. */
- if (!intq_full (&buffer))
+ if (!input_full ())
{
key_cnt++;
- intq_putc (&buffer, c);
+ input_putc (c);
}
}
}
#include <stdint.h>
void kbd_init (void);
-uint8_t kbd_getc (void);
void kbd_print_stats (void);
#endif /* devices/kbd.h */
#include "devices/serial.h"
#include <debug.h>
+#include "devices/input.h"
#include "devices/intq.h"
#include "devices/timer.h"
#include "threads/io.h"
/* DLAB=0 registers. */
#define RBR_REG (IO_BASE + 0) /* Receiver Buffer Reg. (read-only). */
#define THR_REG (IO_BASE + 0) /* Transmitter Holding Reg. (write-only). */
-#define IER_REG (IO_BASE + 1) /* Interrupt Enable Reg. (read-only). */
-#define FCR_REG (IO_BASE + 2) /* FIFO Control Reg. (write-only). */
-#define LCR_REG (IO_BASE + 3) /* Line Control Register. */
-#define MCR_REG (IO_BASE + 4) /* MODEM Control Register. */
-#define LSR_REG (IO_BASE + 5) /* Line Status Register (read-only). */
+#define IER_REG (IO_BASE + 1) /* Interrupt Enable Reg.. */
/* DLAB=1 registers. */
#define LS_REG (IO_BASE + 0) /* Divisor Latch (LSB). */
#define MS_REG (IO_BASE + 1) /* Divisor Latch (MSB). */
+/* DLAB-insensitive registers. */
+#define IIR_REG (IO_BASE + 2) /* Interrupt Identification Reg. (read-only) */
+#define FCR_REG (IO_BASE + 2) /* FIFO Control Reg. (write-only). */
+#define LCR_REG (IO_BASE + 3) /* Line Control Register. */
+#define MCR_REG (IO_BASE + 4) /* MODEM Control Register. */
+#define LSR_REG (IO_BASE + 5) /* Line Status Register (read-only). */
+
/* Interrupt Enable Register bits. */
+#define IER_RECV 0x01 /* Interrupt when data received. */
#define IER_XMIT 0x02 /* Interrupt when transmit finishes. */
/* Line Control Register bits. */
#define MCR_OUT2 0x08 /* Output line 2. */
/* Line Status Register. */
+#define LSR_DR 0x01 /* Data Ready: received data byte is in RBR. */
#define LSR_THRE 0x20 /* THR Empty. */
\f
/* Transmission mode. */
void
serial_init_queue (void)
{
+ enum intr_level old_level;
+
ASSERT (mode == POLL);
+
intr_register_ext (0x20 + 4, serial_interrupt, "serial");
mode = QUEUE;
+ old_level = intr_disable ();
+ write_ier ();
+ intr_set_level (old_level);
}
/* Sends BYTE to the serial port. */
putc_poll (intq_getc (&txq));
intr_set_level (old_level);
}
+
+/* The fullness of the input buffer may have changed. Reassess
+ whether we should block receive interrupts.
+ Called by the input buffer routines when characters are added
+ to or removed from the buffer. */
+void
+serial_notify (void)
+{
+ ASSERT (intr_get_level () == INTR_OFF);
+ if (mode == QUEUE)
+ write_ier ();
+}
\f
/* Configures the serial port for BPS bits per second. */
static void
set_serial (int bps)
{
- int baud_base = 1843200 / 16; /* Base rate of 16550A. */
- uint16_t divisor = baud_base / bps; /* Clock rate divisor. */
+ int base_rate = 1843200 / 16; /* Base rate of 16550A, in Hz. */
+ uint16_t divisor = base_rate / bps; /* Clock rate divisor. */
ASSERT (bps >= 300 && bps <= 115200);
/* Enable DLAB. */
outb (LCR_REG, LCR_N81 | LCR_DLAB);
- /* Set baud rate. */
+ /* Set data rate. */
outb (LS_REG, divisor & 0xff);
outb (MS_REG, divisor >> 8);
outb (LCR_REG, LCR_N81);
}
-/* Update interrupt enable register.
- If our transmit queue is empty, turn off transmit interrupt. */
+/* Update interrupt enable register. */
static void
write_ier (void)
{
- outb (IER_REG, intq_empty (&txq) ? 0 : IER_XMIT);
+ uint8_t ier = 0;
+
+ ASSERT (intr_get_level () == INTR_OFF);
+
+ /* Enable transmit interrupt if we have any characters to
+ transmit. */
+ if (!intq_empty (&txq))
+ ier |= IER_XMIT;
+
+ /* Enable receive interrupt if we have room to store any
+ characters we receive. */
+ if (!input_full ())
+ ier |= IER_RECV;
+
+ outb (IER_REG, ier);
}
/* Polls the serial port until it's ready,
outb (THR_REG, byte);
}
-/* Serial interrupt handler.
- As long as we have a byte to transmit,
- and the hardware is ready to accept a byte for transmission,
- transmit a byte.
- Then update interrupt enable register based on queue
- status. */
+/* Serial interrupt handler. */
static void
serial_interrupt (struct intr_frame *f UNUSED)
{
+ /* Inquire about interrupt in UART. Without this, we can
+ occasionally miss an interrupt running under qemu. */
+ inb (IIR_REG);
+
+ /* As long as we have room to receive a byte, and the hardware
+ has a byte for us, receive a byte. */
+ while (!input_full () && (inb (LSR_REG) & LSR_DR) != 0)
+ input_putc (inb (RBR_REG));
+
+ /* As long as we have a byte to transmit, and the hardware is
+ ready to accept a byte for transmission, transmit a byte. */
while (!intq_empty (&txq) && (inb (LSR_REG) & LSR_THRE) != 0)
outb (THR_REG, intq_getc (&txq));
+
+ /* Update interrupt enable register based on queue status. */
write_ier ();
}
void serial_init_queue (void);
void serial_putc (uint8_t);
void serial_flush (void);
+void serial_notify (void);
#endif /* devices/serial.h */
switch (c)
{
- case '\n':
+ case '\r':
*pos = '\0';
putchar ('\n');
return;
TESTCMD += -f
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
$(TESTCMD)
GETCMD += -- -q
GETCMD += $(KERNELFLAGS)
GETCMD += run 'tar fs.tar /'
-GETCMD += 2>> $(TEST).get-errors
-GETCMD += $(if $(VERBOSE),|tee -a,>>) $(TEST).get-output
+GETCMD += < /dev/null
+GETCMD += 2> $(TEST).get-errors $(if $(VERBOSE),|tee,>) $(TEST).get-output
tests/filesys/extended/%.output: os.dsk
rm -f tmp.dsk
#include <stdlib.h>
#include <string.h>
#include "devices/kbd.h"
+#include "devices/input.h"
#include "devices/serial.h"
#include "devices/timer.h"
#include "devices/vga.h"
intr_init ();
timer_init ();
kbd_init ();
+ input_init ();
#ifdef USERPROG
exception_init ();
syscall_init ();
setitimer-helper
+squish-pty
-all: setitimer-helper
+all: setitimer-helper squish-pty
+CC = gcc
+CFLAGS = -Wall -W
LDFLAGS = -lm
setitimer-helper: setitimer-helper.o
+squish-pty: squish-pty.o
clean:
- rm -f *.o setitimer-helper
+ rm -f *.o setitimer-helper squish-pty
+If you want to be able to give input to Pintos running in Bochs
+through a terminal (as opposed to a VGA console window), you will need
+to compile squish-pty (with `make') and install it in PATH.
+Otherwise, squish-pty is unneeded.
+
If your version of Perl predates 5.8.0 and you want the "pintos"
script's timeout support (with -T) to properly limit the child's CPU
time, then you will need to compile setitimer-helper (with `make') and
our ($sim); # Simulator: bochs, qemu, or gsx.
our ($debug) = "none"; # Debugger: none, monitor, or gdb.
our ($mem) = 4; # Physical RAM in MB.
-our ($serial_out) = 1; # Send output to serial port?
+our ($serial) = 1; # Use serial port for input and output?
our ($vga); # VGA output: window, terminal, or none.
our ($jitter); # Seed for random timer interrupts, if set.
our ($realtime); # Synchronize timer interrupts with real time?
"k|kill-on-failure" => \$kill_on_failure,
"v|no-vga" => sub { set_vga ('none'); },
- "s|no-serial" => sub { $serial_out = 0; },
+ "s|no-serial" => sub { $serial = 0; },
"t|terminal" => sub { set_vga ('terminal'); },
"p|put-file=s" => sub { add_file (\@puts, $_[1]); },
undef $timeout, print "warning: disabling timeout with --$debug\n"
if defined ($timeout) && $debug ne 'none';
- print "warning: enabling serial output for -k or --kill-on-failure\n"
- if $kill_on_failure && !$serial_out;
+ print "warning: enabling serial port for -k or --kill-on-failure\n"
+ if $kill_on_failure && !$serial;
}
# usage($exitcode).
--monitor Debug with simulator's monitor
--gdb Debug with gdb
Display options: (default is both VGA and serial)
- -v, --no-vga No VGA display
- -s, --no-serial No serial output
+ -v, --no-vga No VGA display or keyboard
+ -s, --no-serial No serial input or output
-t, --terminal Display VGA in terminal (Bochs only)
Timing options: (Bochs only)
-j SEED Randomize timer interrupts
# Select Bochs binary based on the chosen debugger.
my ($bin) = $debug eq 'monitor' ? 'bochs-dbg' : 'bochs';
+ my ($squish_pty);
+ if ($serial) {
+ for my $dir (split (':', $ENV{PATH})) {
+ $squish_pty = "$dir/squish-pty", last if -x "$dir/squish-pty";
+ }
+ print "warning: can't find squish-pty, so terminal input will fail\n"
+ if !defined $squish_pty;
+ }
+
# Write bochsrc.txt configuration file.
open (BOCHSRC, ">", "bochsrc.txt") or die "bochsrc.txt: create: $!\n";
print BOCHSRC <<EOF;
print_bochs_disk_line ("ata1-slave", 3);
}
if ($vga ne 'terminal') {
- print BOCHSRC "com1: enabled=1, mode=file, dev=/dev/stdout\n"
- if $serial_out;
+ if ($serial) {
+ my $mode = defined ($squish_pty) ? "term" : "file";
+ print BOCHSRC "com1: enabled=1, mode=$mode, dev=/dev/stdout\n";
+ }
print BOCHSRC "display_library: nogui\n" if $vga eq 'none';
} else {
print BOCHSRC "display_library: term\n";
# Compose Bochs command line.
my (@cmd) = ($bin, '-q');
+ unshift (@cmd, $squish_pty) if defined $squish_pty;
push (@cmd, '-j', $jitter) if defined $jitter;
# Run Bochs.
push (@cmd, '-m', $mem);
push (@cmd, '-net', 'none');
push (@cmd, '-nographic') if $vga eq 'none';
- push (@cmd, '-serial', 'stdio') if $serial_out && $vga ne 'none';
+ push (@cmd, '-serial', 'stdio') if $serial && $vga ne 'none';
push (@cmd, '-S') if $debug eq 'monitor';
push (@cmd, '-s', '-S') if $debug eq 'gdb';
push (@cmd, '-monitor', 'null') if $vga eq 'none' && $debug eq 'none';
--- /dev/null
+#define _GNU_SOURCE 1
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stropts.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <termios.h>
+#include <unistd.h>
+
+static void
+fail_io (const char *msg, ...)
+ __attribute__ ((noreturn))
+ __attribute__ ((format (printf, 1, 2)));
+
+/* Prints MSG, formatting as with printf(),
+ plus an error message based on errno,
+ and exits. */
+static void
+fail_io (const char *msg, ...)
+{
+ va_list args;
+
+ va_start (args, msg);
+ vfprintf (stderr, msg, args);
+ va_end (args);
+
+ if (errno != 0)
+ fprintf (stderr, ": %s", strerror (errno));
+ putc ('\n', stderr);
+ exit (EXIT_FAILURE);
+}
+
+/* If FD is a terminal, configures it for noncanonical input mode
+ with VMIN and VTIME set as indicated.
+ If FD is not a terminal, has no effect. */
+static void
+make_noncanon (int fd, int vmin, int vtime)
+{
+ if (isatty (fd))
+ {
+ struct termios termios;
+ if (tcgetattr (fd, &termios) < 0)
+ fail_io ("tcgetattr");
+ termios.c_lflag &= ~(ICANON | ECHO);
+ termios.c_cc[VMIN] = vmin;
+ termios.c_cc[VTIME] = vtime;
+ if (tcsetattr (fd, TCSANOW, &termios) < 0)
+ fail_io ("tcsetattr");
+ }
+}
+
+/* Make FD non-blocking if NONBLOCKING is true,
+ or blocking if NONBLOCKING is false. */
+static void
+make_nonblocking (int fd, bool nonblocking)
+{
+ int flags = fcntl (fd, F_GETFL);
+ if (flags < 0)
+ fail_io ("fcntl");
+ if (nonblocking)
+ flags |= O_NONBLOCK;
+ else
+ flags &= ~O_NONBLOCK;
+ if (fcntl (fd, F_SETFL, flags) < 0)
+ fail_io ("fcntl");
+}
+
+/* Handle a read or write on *FD, which is the pty if FD_IS_PTY
+ is true, that returned end-of-file or error indication RETVAL.
+ The system call is named CALL, for use in error messages.
+ Returns true if processing may continue, false if we're all
+ done. */
+static bool
+handle_error (ssize_t retval, int *fd, bool fd_is_pty, const char *call)
+{
+ if (fd_is_pty)
+ {
+ if (retval < 0)
+ {
+ if (errno == EIO)
+ {
+ /* Slave side of pty has been closed. */
+ return false;
+ }
+ else
+ fail_io (call);
+ }
+ else
+ return true;
+ }
+ else
+ {
+ if (retval == 0)
+ {
+ close (*fd);
+ *fd = -1;
+ return true;
+ }
+ else
+ fail_io (call);
+ }
+}
+
+/* Copies data from stdin to PTY and from PTY to stdout until no
+ more data can be read or written. */
+static void
+relay (int pty, int dead_child_fd)
+{
+ struct pipe
+ {
+ int in, out;
+ char buf[BUFSIZ];
+ size_t size, ofs;
+ bool active;
+ };
+ struct pipe pipes[2];
+
+ /* Make PTY, stdin, and stdout non-blocking. */
+ make_nonblocking (pty, true);
+ make_nonblocking (STDIN_FILENO, true);
+ make_nonblocking (STDOUT_FILENO, true);
+
+ /* Configure noncanonical mode on PTY and stdin to avoid
+ waiting for end-of-line. We want to minimize context
+ switching on PTY (for efficiency) and minimize latency on
+ stdin to avoid a laggy user experience. */
+ make_noncanon (pty, 16, 1);
+ make_noncanon (STDIN_FILENO, 1, 0);
+
+ memset (pipes, 0, sizeof pipes);
+ pipes[0].in = STDIN_FILENO;
+ pipes[0].out = pty;
+ pipes[1].in = pty;
+ pipes[1].out = STDOUT_FILENO;
+
+ while (pipes[0].in != -1 || pipes[1].in != -1)
+ {
+ fd_set read_fds, write_fds;
+ int retval;
+ int i;
+
+ FD_ZERO (&read_fds);
+ FD_ZERO (&write_fds);
+ for (i = 0; i < 2; i++)
+ {
+ struct pipe *p = &pipes[i];
+
+ /* Don't do anything with the stdin->pty pipe until we
+ have some data for the pty->stdout pipe. If we get
+ too eager, Bochs will throw away our input. */
+ if (i == 0 && !pipes[1].active)
+ continue;
+
+ if (p->in != -1 && p->size + p->ofs < sizeof p->buf)
+ FD_SET (p->in, &read_fds);
+ if (p->out != -1 && p->size > 0)
+ FD_SET (p->out, &write_fds);
+ }
+ FD_SET (dead_child_fd, &read_fds);
+
+ do
+ {
+ retval = select (FD_SETSIZE, &read_fds, &write_fds, NULL, NULL);
+ }
+ while (retval < 0 && errno == EINTR);
+ if (retval < 0)
+ fail_io ("select");
+
+ if (FD_ISSET (dead_child_fd, &read_fds))
+ {
+ /* Child died. Do final relaying. */
+ struct pipe *p = &pipes[1];
+ if (p->out == -1)
+ return;
+ make_nonblocking (STDOUT_FILENO, false);
+ for (;;)
+ {
+ ssize_t n;
+
+ /* Write buffer. */
+ while (p->size > 0)
+ {
+ n = write (p->out, p->buf + p->ofs, p->size);
+ if (n < 0)
+ fail_io ("write");
+ else if (n == 0)
+ fail_io ("zero-length write");
+ p->ofs += n;
+ p->size -= n;
+ }
+ p->ofs = 0;
+
+ p->size = n = read (p->in, p->buf, sizeof p->buf);
+ if (n <= 0)
+ return;
+ }
+ }
+
+ for (i = 0; i < 2; i++)
+ {
+ struct pipe *p = &pipes[i];
+ if (p->in != -1 && FD_ISSET (p->in, &read_fds))
+ {
+ ssize_t n = read (p->in, p->buf + p->ofs + p->size,
+ sizeof p->buf - p->ofs - p->size);
+ if (n > 0)
+ {
+ p->active = true;
+ p->size += n;
+ if (p->size == BUFSIZ && p->ofs != 0)
+ {
+ memmove (p->buf, p->buf + p->ofs, p->size);
+ p->ofs = 0;
+ }
+ }
+ else if (!handle_error (n, &p->in, p->in == pty, "read"))
+ return;
+ }
+ if (p->out != -1 && FD_ISSET (p->out, &write_fds))
+ {
+ ssize_t n = write (p->out, p->buf + p->ofs, p->size);
+ if (n > 0)
+ {
+ p->ofs += n;
+ p->size -= n;
+ if (p->size == 0)
+ p->ofs = 0;
+ }
+ else if (!handle_error (n, &p->out, p->out == pty, "write"))
+ return;
+ }
+ }
+ }
+}
+
+static int dead_child_fd;
+
+static void
+sigchld_handler (int signo __attribute__ ((unused)))
+{
+ if (write (dead_child_fd, "", 1) < 0)
+ _exit (1);
+}
+
+int
+main (int argc __attribute__ ((unused)), char *argv[])
+{
+ int master, slave;
+ char *name;
+ pid_t pid;
+ struct sigaction sa;
+ int pipe_fds[2];
+ struct itimerval zero_itimerval, old_itimerval;
+
+ if (argc < 2)
+ {
+ fprintf (stderr,
+ "usage: squish-pty COMMAND [ARG]...\n"
+ "Squishes both stdin and stdout into a single pseudoterminal,\n"
+ "which is passed as stdout to run the specified COMMAND.\n");
+ return EXIT_FAILURE;
+ }
+
+ /* Open master side of pty and get ready to open slave. */
+ master = open ("/dev/ptmx", O_RDWR | O_NOCTTY);
+ if (master < 0)
+ fail_io ("open \"/dev/ptmx\"");
+ if (grantpt (master) < 0)
+ fail_io ("grantpt");
+ if (unlockpt (master) < 0)
+ fail_io ("unlockpt");
+
+ /* Open slave side of pty. */
+ name = ptsname (master);
+ if (name == NULL)
+ fail_io ("ptsname");
+ slave = open (name, O_RDWR);
+ if (slave < 0)
+ fail_io ("open \"%s\"", name);
+
+ /* System V implementations need STREAMS configuration for the
+ slave. */
+ if (isastream (slave))
+ {
+ if (ioctl (slave, I_PUSH, "ptem") < 0
+ || ioctl (slave, I_PUSH, "ldterm") < 0)
+ fail_io ("ioctl");
+ }
+
+ /* Arrange to get notified when a child dies, by writing a byte
+ to a pipe fd. We really want to use pselect() and
+ sigprocmask(), but Solaris 2.7 doesn't have it. */
+ if (pipe (pipe_fds) < 0)
+ fail_io ("pipe");
+ dead_child_fd = pipe_fds[1];
+
+ memset (&sa, 0, sizeof sa);
+ sa.sa_handler = sigchld_handler;
+ sigemptyset (&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ if (sigaction (SIGCHLD, &sa, NULL) < 0)
+ fail_io ("sigaction");
+
+ /* Save the virtual interval timer, which might have been set
+ by the process that ran us. It really should be applied to
+ our child process. */
+ memset (&zero_itimerval, 0, sizeof zero_itimerval);
+ if (setitimer (ITIMER_VIRTUAL, &zero_itimerval, &old_itimerval) < 0)
+ fail_io ("setitimer");
+
+ pid = fork ();
+ if (pid < 0)
+ fail_io ("fork");
+ else if (pid != 0)
+ {
+ /* Running in parent process. */
+ int status;
+ close (slave);
+ relay (master, pipe_fds[0]);
+
+ /* If the subprocess has died, die in the same fashion.
+ In particular, dying from SIGVTALRM tells the pintos
+ script that we ran out of CPU time. */
+ if (waitpid (pid, &status, WNOHANG) > 0)
+ {
+ if (WIFEXITED (status))
+ return WEXITSTATUS (status);
+ else if (WIFSIGNALED (status))
+ raise (WTERMSIG (status));
+ }
+ return 0;
+ }
+ else
+ {
+ /* Running in child process. */
+ if (setitimer (ITIMER_VIRTUAL, &old_itimerval, NULL) < 0)
+ fail_io ("setitimer");
+ if (dup2 (slave, STDOUT_FILENO) < 0)
+ fail_io ("dup2");
+ if (close (pipe_fds[0]) < 0 || close (pipe_fds[1]) < 0
+ || close (slave) < 0 || close (master) < 0)
+ fail_io ("close");
+ execvp (argv[1], argv + 1);
+ fail_io ("exec");
+ }
+}