provided code

This commit is contained in:
LabTS
2024-10-01 23:37:39 +01:00
commit 8724a2641e
697 changed files with 74252 additions and 0 deletions

3
src/devices/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
build
bochsrc.txt
bochsout.txt

7
src/devices/Make.vars Normal file
View File

@@ -0,0 +1,7 @@
# -*- makefile -*-
kernel.bin: DEFINES =
KERNEL_SUBDIRS = threads devices lib lib/kernel $(TEST_SUBDIRS)
TEST_SUBDIRS = tests/devices
GRADING_FILE = $(SRCDIR)/tests/devices/Grading
SIMULATOR = --qemu

1
src/devices/Makefile Normal file
View File

@@ -0,0 +1 @@
include ../Makefile.kernel

223
src/devices/block.c Normal file
View File

@@ -0,0 +1,223 @@
#include "devices/block.h"
#include <list.h>
#include <string.h>
#include <stdio.h>
#include "devices/ide.h"
#include "threads/malloc.h"
/* A block device. */
struct block
{
struct list_elem list_elem; /* Element in all_blocks. */
char name[16]; /* Block device name. */
enum block_type type; /* Type of block device. */
block_sector_t size; /* Size in sectors. */
const struct block_operations *ops; /* Driver operations. */
void *aux; /* Extra data owned by driver. */
unsigned long long read_cnt; /* Number of sectors read. */
unsigned long long write_cnt; /* Number of sectors written. */
};
/* List of all block devices. */
static struct list all_blocks = LIST_INITIALIZER (all_blocks);
/* The block block assigned to each PintOS role. */
static struct block *block_by_role[BLOCK_ROLE_CNT];
static struct block *list_elem_to_block (struct list_elem *);
/* Returns a human-readable name for the given block device
TYPE. */
const char *
block_type_name (enum block_type type)
{
static const char *block_type_names[BLOCK_CNT] =
{
"kernel",
"filesys",
"scratch",
"swap",
"raw",
"foreign",
};
ASSERT (type < BLOCK_CNT);
return block_type_names[type];
}
/* Returns the block device fulfilling the given ROLE, or a null
pointer if no block device has been assigned that role. */
struct block *
block_get_role (enum block_type role)
{
ASSERT (role < BLOCK_ROLE_CNT);
return block_by_role[role];
}
/* Assigns BLOCK the given ROLE. */
void
block_set_role (enum block_type role, struct block *block)
{
ASSERT (role < BLOCK_ROLE_CNT);
block_by_role[role] = block;
}
/* Returns the first block device in kernel probe order, or a
null pointer if no block devices are registered. */
struct block *
block_first (void)
{
return list_elem_to_block (list_begin (&all_blocks));
}
/* Returns the block device following BLOCK in kernel probe
order, or a null pointer if BLOCK is the last block device. */
struct block *
block_next (struct block *block)
{
return list_elem_to_block (list_next (&block->list_elem));
}
/* Returns the block device with the given NAME, or a null
pointer if no block device has that name. */
struct block *
block_get_by_name (const char *name)
{
struct list_elem *e;
for (e = list_begin (&all_blocks); e != list_end (&all_blocks);
e = list_next (e))
{
struct block *block = list_entry (e, struct block, list_elem);
if (!strcmp (name, block->name))
return block;
}
return NULL;
}
/* Verifies that SECTOR is a valid offset within BLOCK.
Panics if not. */
static void
check_sector (struct block *block, block_sector_t sector)
{
if (sector >= block->size)
{
/* We do not use ASSERT because we want to panic here
regardless of whether NDEBUG is defined. */
PANIC ("Access past end of device %s (sector=%"PRDSNu", "
"size=%"PRDSNu")\n", block_name (block), sector, block->size);
}
}
/* Reads sector SECTOR from BLOCK into BUFFER, which must
have room for BLOCK_SECTOR_SIZE bytes.
Internally synchronizes accesses to block devices, so external
per-block device locking is unneeded. */
void
block_read (struct block *block, block_sector_t sector, void *buffer)
{
check_sector (block, sector);
block->ops->read (block->aux, sector, buffer);
block->read_cnt++;
}
/* Write sector SECTOR to BLOCK from BUFFER, which must contain
BLOCK_SECTOR_SIZE bytes. Returns after the block device has
acknowledged receiving the data.
Internally synchronizes accesses to block devices, so external
per-block device locking is unneeded. */
void
block_write (struct block *block, block_sector_t sector, const void *buffer)
{
check_sector (block, sector);
ASSERT (block->type != BLOCK_FOREIGN);
block->ops->write (block->aux, sector, buffer);
block->write_cnt++;
}
/* Returns the number of sectors in BLOCK. */
block_sector_t
block_size (struct block *block)
{
return block->size;
}
/* Returns BLOCK's name (e.g. "hda"). */
const char *
block_name (struct block *block)
{
return block->name;
}
/* Returns BLOCK's type. */
enum block_type
block_type (struct block *block)
{
return block->type;
}
/* Prints statistics for each block device used for a PintOS role. */
void
block_print_stats (void)
{
int i;
for (i = 0; i < BLOCK_ROLE_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);
}

74
src/devices/block.h Normal file
View File

@@ -0,0 +1,74 @@
#ifndef DEVICES_BLOCK_H
#define DEVICES_BLOCK_H
#include <stddef.h>
#include <inttypes.h>
/* Size of a block device sector in bytes.
All IDE disks use this sector size, as do most USB and SCSI
disks. It's not worth it to try to cater to other sector
sizes in PintOS (yet). */
#define BLOCK_SECTOR_SIZE 512
/* Index of a block device sector.
Good enough for devices up to 2 TB. */
typedef uint32_t block_sector_t;
/* Format specifier for printf(), e.g.:
printf ("sector=%"PRDSNu"\n", sector); */
#define PRDSNu PRIu32
/* 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 */

527
src/devices/ide.c Normal file
View File

@@ -0,0 +1,527 @@
#include "devices/ide.h"
#include <ctype.h>
#include <debug.h>
#include <stdbool.h>
#include <stdio.h>
#include "devices/block.h"
#include "devices/partition.h"
#include "devices/timer.h"
#include "threads/io.h"
#include "threads/interrupt.h"
#include "threads/synch.h"
/* The code in this file is an interface to an ATA (IDE)
controller. It attempts to comply to [ATA-3]. */
/* ATA command block port addresses. */
#define reg_data(CHANNEL) ((CHANNEL)->reg_base + 0) /* Data. */
#define reg_error(CHANNEL) ((CHANNEL)->reg_base + 1) /* Error. */
#define reg_nsect(CHANNEL) ((CHANNEL)->reg_base + 2) /* Sector Count. */
#define reg_lbal(CHANNEL) ((CHANNEL)->reg_base + 3) /* LBA 0:7. */
#define reg_lbam(CHANNEL) ((CHANNEL)->reg_base + 4) /* LBA 15:8. */
#define reg_lbah(CHANNEL) ((CHANNEL)->reg_base + 5) /* LBA 23:16. */
#define reg_device(CHANNEL) ((CHANNEL)->reg_base + 6) /* Device/LBA 27:24. */
#define reg_status(CHANNEL) ((CHANNEL)->reg_base + 7) /* Status (r/o). */
#define reg_command(CHANNEL) reg_status (CHANNEL) /* Command (w/o). */
/* ATA control block port addresses.
(If we supported non-legacy ATA controllers this would not be
flexible enough, but it's fine for what we do.) */
#define reg_ctl(CHANNEL) ((CHANNEL)->reg_base + 0x206) /* Control (w/o). */
#define reg_alt_status(CHANNEL) reg_ctl (CHANNEL) /* Alt Status (r/o). */
/* Alternate Status Register bits. */
#define STA_BSY 0x80 /* Busy. */
#define STA_DRDY 0x40 /* Device Ready. */
#define STA_DRQ 0x08 /* Data Request. */
/* Control Register bits. */
#define CTL_SRST 0x04 /* Software Reset. */
/* Device Register bits. */
#define DEV_MBS 0xa0 /* Must be set. */
#define DEV_LBA 0x40 /* Linear based addressing. */
#define DEV_DEV 0x10 /* Select device: 0=master, 1=slave. */
/* Commands.
Many more are defined but this is the small subset that we
use. */
#define CMD_IDENTIFY_DEVICE 0xec /* IDENTIFY DEVICE. */
#define CMD_READ_SECTOR_RETRY 0x20 /* READ SECTOR with retries. */
#define CMD_WRITE_SECTOR_RETRY 0x30 /* WRITE SECTOR with retries. */
/* An ATA device. */
struct ata_disk
{
char name[8]; /* Name, e.g. "hda". */
struct channel *channel; /* Channel that disk is attached to. */
int dev_no; /* Device 0 or 1 for master or slave. */
bool is_ata; /* Is device an ATA disk? */
};
/* An ATA channel (aka controller).
Each channel can control up to two disks. */
struct channel
{
char name[8]; /* Name, e.g. "ide0". */
uint16_t reg_base; /* Base I/O port. */
uint8_t irq; /* Interrupt in use. */
struct lock lock; /* Must acquire to access the controller. */
bool expecting_interrupt; /* True if an interrupt is expected, false if
any interrupt would be spurious. */
struct semaphore completion_wait; /* Up'd by interrupt handler. */
struct ata_disk devices[2]; /* The devices on this channel. */
};
/* We support the two "legacy" ATA channels found in a standard PC. */
#define CHANNEL_CNT 2
static struct channel channels[CHANNEL_CNT];
static struct block_operations ide_operations;
static void reset_channel (struct channel *);
static bool check_device_type (struct ata_disk *);
static void identify_ata_device (struct ata_disk *);
static void select_sector (struct ata_disk *, block_sector_t);
static void issue_pio_command (struct channel *, uint8_t command);
static void input_sector (struct channel *, void *);
static void output_sector (struct channel *, const void *);
static void wait_until_idle (const struct ata_disk *);
static bool wait_while_busy (const struct ata_disk *);
static void select_device (const struct ata_disk *);
static void select_device_wait (const struct ata_disk *);
static void interrupt_handler (struct intr_frame *);
/* Initialize the disk subsystem and detect disks. */
void
ide_init (void)
{
size_t chan_no;
for (chan_no = 0; chan_no < CHANNEL_CNT; chan_no++)
{
struct channel *c = &channels[chan_no];
int dev_no;
/* Initialize channel. */
snprintf (c->name, sizeof c->name, "ide%zu", chan_no);
switch (chan_no)
{
case 0:
c->reg_base = 0x1f0;
c->irq = 14 + 0x20;
break;
case 1:
c->reg_base = 0x170;
c->irq = 15 + 0x20;
break;
default:
NOT_REACHED ();
}
lock_init (&c->lock);
c->expecting_interrupt = false;
sema_init (&c->completion_wait, 0);
/* Initialize devices. */
for (dev_no = 0; dev_no < 2; dev_no++)
{
struct ata_disk *d = &c->devices[dev_no];
snprintf (d->name, sizeof d->name,
"hd%c", 'a' + chan_no * 2 + dev_no);
d->channel = c;
d->dev_no = dev_no;
d->is_ata = false;
}
/* Register interrupt handler. */
intr_register_ext (c->irq, interrupt_handler, c->name);
/* Reset hardware. */
reset_channel (c);
/* Distinguish ATA hard disks from other devices. */
if (check_device_type (&c->devices[0]))
check_device_type (&c->devices[1]);
/* Read hard disk identity information. */
for (dev_no = 0; dev_no < 2; dev_no++)
if (c->devices[dev_no].is_ata)
identify_ata_device (&c->devices[dev_no]);
}
}
/* 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 ();
}

6
src/devices/ide.h Normal file
View File

@@ -0,0 +1,6 @@
#ifndef DEVICES_IDE_H
#define DEVICES_IDE_H
void ide_init (void);
#endif /* devices/ide.h */

52
src/devices/input.c Normal file
View File

@@ -0,0 +1,52 @@
#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);
}

12
src/devices/input.h Normal file
View File

@@ -0,0 +1,12 @@
#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 */

114
src/devices/intq.c Normal file
View File

@@ -0,0 +1,114 @@
#include "devices/intq.h"
#include <debug.h>
#include "threads/thread.h"
static int next (int pos);
static void wait (struct intq *q, struct thread **waiter);
static void signal (struct intq *q, struct thread **waiter);
/* Initializes interrupt queue Q. */
void
intq_init (struct intq *q)
{
lock_init (&q->lock);
q->not_full = q->not_empty = NULL;
q->head = q->tail = 0;
}
/* Returns true if Q is empty, false otherwise. */
bool
intq_empty (const struct intq *q)
{
ASSERT (intr_get_level () == INTR_OFF);
return q->head == q->tail;
}
/* Returns true if Q is full, false otherwise. */
bool
intq_full (const struct intq *q)
{
ASSERT (intr_get_level () == INTR_OFF);
return next (q->head) == q->tail;
}
/* Removes a byte from Q and returns it.
If Q is empty, sleeps until a byte is added.
When called from an interrupt handler, Q must not be empty. */
uint8_t
intq_getc (struct intq *q)
{
uint8_t byte;
ASSERT (intr_get_level () == INTR_OFF);
while (intq_empty (q))
{
ASSERT (!intr_context ());
lock_acquire (&q->lock);
wait (q, &q->not_empty);
lock_release (&q->lock);
}
byte = q->buf[q->tail];
q->tail = next (q->tail);
signal (q, &q->not_full);
return byte;
}
/* Adds BYTE to the end of Q.
If Q is full, sleeps until a byte is removed.
When called from an interrupt handler, Q must not be full. */
void
intq_putc (struct intq *q, uint8_t byte)
{
ASSERT (intr_get_level () == INTR_OFF);
while (intq_full (q))
{
ASSERT (!intr_context ());
lock_acquire (&q->lock);
wait (q, &q->not_full);
lock_release (&q->lock);
}
q->buf[q->head] = byte;
q->head = next (q->head);
signal (q, &q->not_empty);
}
/* Returns the position after POS within an intq. */
static int
next (int pos)
{
return (pos + 1) % INTQ_BUFSIZE;
}
/* WAITER must be the address of Q's not_empty or not_full
member. Waits until the given condition is true. */
static void
wait (struct intq *q UNUSED, struct thread **waiter)
{
ASSERT (!intr_context ());
ASSERT (intr_get_level () == INTR_OFF);
ASSERT ((waiter == &q->not_empty && intq_empty (q))
|| (waiter == &q->not_full && intq_full (q)));
*waiter = thread_current ();
thread_block ();
}
/* WAITER must be the address of Q's not_empty or not_full
member, and the associated condition must be true. If a
thread is waiting for the condition, wakes it up and resets
the waiting thread. */
static void
signal (struct intq *q UNUSED, struct thread **waiter)
{
ASSERT (intr_get_level () == INTR_OFF);
ASSERT ((waiter == &q->not_empty && !intq_empty (q))
|| (waiter == &q->not_full && !intq_full (q)));
if (*waiter != NULL)
{
thread_unblock (*waiter);
*waiter = NULL;
}
}

43
src/devices/intq.h Normal file
View File

@@ -0,0 +1,43 @@
#ifndef DEVICES_INTQ_H
#define DEVICES_INTQ_H
#include "threads/interrupt.h"
#include "threads/synch.h"
/* An "interrupt queue", a circular buffer shared between
kernel threads and external interrupt handlers.
Interrupt queue functions can be called from kernel threads or
from external interrupt handlers. Except for intq_init(),
interrupts must be off in either case.
The interrupt queue has the structure of a "monitor". Locks
and condition variables from threads/synch.h cannot be used in
this case, as they normally would, because they can only
protect kernel threads from one another, not from interrupt
handlers. */
/* Queue buffer size, in bytes. */
#define INTQ_BUFSIZE 64
/* A circular queue of bytes. */
struct intq
{
/* Waiting threads. */
struct lock lock; /* Only one thread may wait at once. */
struct thread *not_full; /* Thread waiting for not-full condition. */
struct thread *not_empty; /* Thread waiting for not-empty condition. */
/* Queue. */
uint8_t buf[INTQ_BUFSIZE]; /* Buffer. */
int head; /* New data is written here. */
int tail; /* Old data is read here. */
};
void intq_init (struct intq *);
bool intq_empty (const struct intq *);
bool intq_full (const struct intq *);
uint8_t intq_getc (struct intq *);
void intq_putc (struct intq *, uint8_t);
#endif /* devices/intq.h */

213
src/devices/kbd.c Normal file
View File

@@ -0,0 +1,213 @@
#include "devices/kbd.h"
#include <ctype.h>
#include <debug.h>
#include <stdio.h>
#include <string.h>
#include "devices/input.h"
#include "devices/shutdown.h"
#include "threads/interrupt.h"
#include "threads/io.h"
/* Keyboard data register port. */
#define DATA_REG 0x60
/* Current state of shift keys.
True if depressed, false otherwise. */
static bool left_shift, right_shift; /* Left and right Shift keys. */
static bool left_alt, right_alt; /* Left and right Alt keys. */
static bool left_ctrl, right_ctrl; /* Left and right Ctl keys. */
/* Status of Caps Lock.
True when on, false when off. */
static bool caps_lock;
/* Number of keys pressed. */
static int64_t key_cnt;
static intr_handler_func keyboard_interrupt;
/* Initializes the keyboard. */
void
kbd_init (void)
{
intr_register_ext (0x21, keyboard_interrupt, "8042 Keyboard");
}
/* Prints keyboard statistics. */
void
kbd_print_stats (void)
{
printf ("Keyboard: %lld keys pressed\n", key_cnt);
}
/* Maps a set of contiguous scancodes into characters. */
struct keymap
{
uint8_t first_scancode; /* First scancode. */
const char *chars; /* chars[0] has scancode first_scancode,
chars[1] has scancode first_scancode + 1,
and so on to the end of the string. */
};
/* Keys that produce the same characters regardless of whether
the Shift keys are down. Case of letters is an exception
that we handle elsewhere. */
static const struct keymap invariant_keymap[] =
{
{0x01, "\033"}, /* Escape. */
{0x0e, "\b"},
{0x0f, "\tQWERTYUIOP"},
{0x1c, "\r"},
{0x1e, "ASDFGHJKL"},
{0x2c, "ZXCVBNM"},
{0x37, "*"},
{0x39, " "},
{0x53, "\177"}, /* Delete. */
{0, NULL},
};
/* Characters for keys pressed without Shift, for those keys
where it matters. */
static const struct keymap unshifted_keymap[] =
{
{0x02, "1234567890-="},
{0x1a, "[]"},
{0x27, ";'`"},
{0x2b, "\\"},
{0x33, ",./"},
{0, NULL},
};
/* Characters for keys pressed with Shift, for those keys where
it matters. */
static const struct keymap shifted_keymap[] =
{
{0x02, "!@#$%^&*()_+"},
{0x1a, "{}"},
{0x27, ":\"~"},
{0x2b, "|"},
{0x33, "<>?"},
{0, NULL},
};
static bool map_key (const struct keymap[], unsigned scancode, uint8_t *);
static void
keyboard_interrupt (struct intr_frame *args UNUSED)
{
/* Status of shift keys. */
bool shift = left_shift || right_shift;
bool alt = left_alt || right_alt;
bool ctrl = left_ctrl || right_ctrl;
/* Keyboard scancode. */
unsigned code;
/* False if key pressed, true if key released. */
bool release;
/* Character that corresponds to `code'. */
uint8_t c;
/* Read scancode, including second byte if prefix code. */
code = inb (DATA_REG);
if (code == 0xe0)
code = (code << 8) | inb (DATA_REG);
/* Bit 0x80 distinguishes key press from key release
(even if there's a prefix). */
release = (code & 0x80) != 0;
code &= ~0x80u;
/* Interpret key. */
if (code == 0x3a)
{
/* Caps Lock. */
if (!release)
caps_lock = !caps_lock;
}
else if (map_key (invariant_keymap, code, &c)
|| (!shift && map_key (unshifted_keymap, code, &c))
|| (shift && map_key (shifted_keymap, code, &c)))
{
/* Ordinary character. */
if (!release)
{
/* Reboot if Ctrl+Alt+Del pressed. */
if (c == 0177 && ctrl && alt)
shutdown_reboot ();
/* Handle Ctrl, Shift.
Note that Ctrl overrides Shift. */
if (ctrl && c >= 0x40 && c < 0x60)
{
/* A is 0x41, Ctrl+A is 0x01, etc. */
c -= 0x40;
}
else if (shift == caps_lock)
c = tolower (c);
/* Handle Alt by setting the high bit.
This 0x80 is unrelated to the one used to
distinguish key press from key release. */
if (alt)
c += 0x80;
/* Append to keyboard buffer. */
if (!input_full ())
{
key_cnt++;
input_putc (c);
}
}
}
else
{
/* Maps a keycode into a shift state variable. */
struct shift_key
{
unsigned scancode;
bool *state_var;
};
/* Table of shift keys. */
static const struct shift_key shift_keys[] =
{
{ 0x2a, &left_shift},
{ 0x36, &right_shift},
{ 0x38, &left_alt},
{0xe038, &right_alt},
{ 0x1d, &left_ctrl},
{0xe01d, &right_ctrl},
{0, NULL},
};
const struct shift_key *key;
/* Scan the table. */
for (key = shift_keys; key->scancode != 0; key++)
if (key->scancode == code)
{
*key->state_var = !release;
break;
}
}
}
/* Scans the array of keymaps K for SCANCODE.
If found, sets *C to the corresponding character and returns
true.
If not found, returns false and C is ignored. */
static bool
map_key (const struct keymap k[], unsigned scancode, uint8_t *c)
{
for (; k->first_scancode != 0; k++)
if (scancode >= k->first_scancode
&& scancode < k->first_scancode + strlen (k->chars))
{
*c = k->chars[scancode - k->first_scancode];
return true;
}
return false;
}

9
src/devices/kbd.h Normal file
View File

@@ -0,0 +1,9 @@
#ifndef DEVICES_KBD_H
#define DEVICES_KBD_H
#include <stdint.h>
void kbd_init (void);
void kbd_print_stats (void);
#endif /* devices/kbd.h */

324
src/devices/partition.c Normal file
View File

@@ -0,0 +1,324 @@
#include "devices/partition.h"
#include <packed.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "devices/block.h"
#include "threads/malloc.h"
/* A partition of a block device. */
struct partition
{
struct block *block; /* Underlying block device. */
block_sector_t start; /* First sector within device. */
};
static struct block_operations partition_operations;
static void read_partition_table (struct block *, block_sector_t sector,
block_sector_t primary_extended_sector,
int *part_nr);
static void found_partition (struct block *, uint8_t type,
block_sector_t start, block_sector_t size,
int part_nr);
static const char *partition_type_name (uint8_t);
/* Scans BLOCK for partitions of interest to PintOS. */
void
partition_scan (struct block *block)
{
int part_nr = 0;
read_partition_table (block, 0, 0, &part_nr);
if (part_nr == 0)
printf ("%s: Device contains no partitions\n", block_name (block));
}
/* Reads the partition table in the given SECTOR of BLOCK and
scans it for partitions of interest to PintOS.
If SECTOR is 0, so that this is the top-level partition table
on BLOCK, then PRIMARY_EXTENDED_SECTOR is not meaningful;
otherwise, it should designate the sector of the top-level
extended partition table that was traversed to arrive at
SECTOR, for use in finding logical partitions (see the large
comment below).
PART_NR points to the number of non-empty primary or logical
partitions already encountered on BLOCK. It is incremented as
partitions are found. */
static void
read_partition_table (struct block *block, block_sector_t sector,
block_sector_t primary_extended_sector,
int *part_nr)
{
/* Format of a partition table entry. See [Partitions]. */
struct partition_table_entry
{
uint8_t bootable; /* 0x00=not bootable, 0x80=bootable. */
uint8_t start_chs[3]; /* Encoded starting cylinder, head, sector. */
uint8_t type; /* Partition type (see partition_type_name). */
uint8_t end_chs[3]; /* Encoded ending cylinder, head, sector. */
uint32_t offset; /* Start sector offset from partition table. */
uint32_t size; /* Number of sectors. */
}
PACKED;
/* Partition table sector. */
struct partition_table
{
uint8_t loader[446]; /* Loader, in top-level partition table. */
struct partition_table_entry partitions[4]; /* Table entries. */
uint16_t signature; /* Should be 0xaa55. */
}
PACKED;
struct partition_table *pt;
size_t i;
/* Check SECTOR validity. */
if (sector >= block_size (block))
{
printf ("%s: Partition table at sector %"PRDSNu" past end of device.\n",
block_name (block), sector);
return;
}
/* Read sector. */
ASSERT (sizeof *pt == BLOCK_SECTOR_SIZE);
pt = malloc (sizeof *pt);
if (pt == NULL)
PANIC ("Failed to allocate memory for partition table.");
block_read (block, 0, pt);
/* Check signature. */
if (pt->signature != 0xaa55)
{
if (primary_extended_sector == 0)
printf ("%s: Invalid partition table signature\n", block_name (block));
else
printf ("%s: Invalid extended partition table in sector %"PRDSNu"\n",
block_name (block), sector);
free (pt);
return;
}
/* Parse partitions. */
for (i = 0; i < sizeof pt->partitions / sizeof *pt->partitions; i++)
{
struct partition_table_entry *e = &pt->partitions[i];
if (e->size == 0 || e->type == 0)
{
/* Ignore empty partition. */
}
else if (e->type == 0x05 /* Extended partition. */
|| e->type == 0x0f /* Windows 98 extended partition. */
|| e->type == 0x85 /* Linux extended partition. */
|| e->type == 0xc5) /* DR-DOS extended partition. */
{
printf ("%s: Extended partition in sector %"PRDSNu"\n",
block_name (block), sector);
/* The interpretation of the offset field for extended
partitions is bizarre. When the extended partition
table entry is in the master boot record, that is,
the device's primary partition table in sector 0, then
the offset is an absolute sector number. Otherwise,
no matter how deep the partition table we're reading
is nested, the offset is relative to the start of
the extended partition that the MBR points to. */
if (sector == 0)
read_partition_table (block, e->offset, e->offset, part_nr);
else
read_partition_table (block, e->offset + primary_extended_sector,
primary_extended_sector, part_nr);
}
else
{
++*part_nr;
found_partition (block, e->type, e->offset + sector,
e->size, *part_nr);
}
}
free (pt);
}
/* We have found a primary or logical partition of the given TYPE
on BLOCK, starting at sector START and continuing for SIZE
sectors, which we are giving the partition number PART_NR.
Check whether this is a partition of interest to PintOS, and
if so then add it to the proper element of partitions[]. */
static void
found_partition (struct block *block, uint8_t part_type,
block_sector_t start, block_sector_t size,
int part_nr)
{
if (start >= block_size (block))
printf ("%s%d: Partition starts past end of device (sector %"PRDSNu")\n",
block_name (block), part_nr, start);
else if (start + size < start || start + size > block_size (block))
printf ("%s%d: Partition end (%"PRDSNu") past end of device (%"PRDSNu")\n",
block_name (block), part_nr, start + size, block_size (block));
else
{
enum block_type type = (part_type == 0x20 ? BLOCK_KERNEL
: part_type == 0x21 ? BLOCK_FILESYS
: part_type == 0x22 ? BLOCK_SCRATCH
: part_type == 0x23 ? BLOCK_SWAP
: BLOCK_FOREIGN);
struct partition *p;
char extra_info[128];
char name[16];
p = malloc (sizeof *p);
if (p == NULL)
PANIC ("Failed to allocate memory for partition descriptor");
p->block = block;
p->start = start;
snprintf (name, sizeof name, "%s%d", block_name (block), part_nr);
snprintf (extra_info, sizeof extra_info, "%s (%02x)",
partition_type_name (part_type), part_type);
block_register (name, type, extra_info, size, &partition_operations, p);
}
}
/* Returns a human-readable name for the given partition TYPE. */
static const char *
partition_type_name (uint8_t type)
{
/* Name of each known type of partition.
From util-linux-2.12r/fdisk/i386_sys_types.c.
This initializer makes use of a C99 feature that allows
array elements to be initialized by index. */
static const char *type_names[256] =
{
[0x00] = "Empty",
[0x01] = "FAT12",
[0x02] = "XENIX root",
[0x03] = "XENIX usr",
[0x04] = "FAT16 <32M",
[0x05] = "Extended",
[0x06] = "FAT16",
[0x07] = "HPFS/NTFS",
[0x08] = "AIX",
[0x09] = "AIX bootable",
[0x0a] = "OS/2 Boot Manager",
[0x0b] = "W95 FAT32",
[0x0c] = "W95 FAT32 (LBA)",
[0x0e] = "W95 FAT16 (LBA)",
[0x0f] = "W95 Ext'd (LBA)",
[0x10] = "OPUS",
[0x11] = "Hidden FAT12",
[0x12] = "Compaq diagnostics",
[0x14] = "Hidden FAT16 <32M",
[0x16] = "Hidden FAT16",
[0x17] = "Hidden HPFS/NTFS",
[0x18] = "AST SmartSleep",
[0x1b] = "Hidden W95 FAT32",
[0x1c] = "Hidden W95 FAT32 (LBA)",
[0x1e] = "Hidden W95 FAT16 (LBA)",
[0x20] = "PintOS OS kernel",
[0x21] = "PintOS file system",
[0x22] = "PintOS scratch",
[0x23] = "PintOS swap",
[0x24] = "NEC DOS",
[0x39] = "Plan 9",
[0x3c] = "PartitionMagic recovery",
[0x40] = "Venix 80286",
[0x41] = "PPC PReP Boot",
[0x42] = "SFS",
[0x4d] = "QNX4.x",
[0x4e] = "QNX4.x 2nd part",
[0x4f] = "QNX4.x 3rd part",
[0x50] = "OnTrack DM",
[0x51] = "OnTrack DM6 Aux1",
[0x52] = "CP/M",
[0x53] = "OnTrack DM6 Aux3",
[0x54] = "OnTrackDM6",
[0x55] = "EZ-Drive",
[0x56] = "Golden Bow",
[0x5c] = "Priam Edisk",
[0x61] = "SpeedStor",
[0x63] = "GNU HURD or SysV",
[0x64] = "Novell Netware 286",
[0x65] = "Novell Netware 386",
[0x70] = "DiskSecure Multi-Boot",
[0x75] = "PC/IX",
[0x80] = "Old Minix",
[0x81] = "Minix / old Linux",
[0x82] = "Linux swap / Solaris",
[0x83] = "Linux",
[0x84] = "OS/2 hidden C: drive",
[0x85] = "Linux extended",
[0x86] = "NTFS volume set",
[0x87] = "NTFS volume set",
[0x88] = "Linux plaintext",
[0x8e] = "Linux LVM",
[0x93] = "Amoeba",
[0x94] = "Amoeba BBT",
[0x9f] = "BSD/OS",
[0xa0] = "IBM Thinkpad hibernation",
[0xa5] = "FreeBSD",
[0xa6] = "OpenBSD",
[0xa7] = "NeXTSTEP",
[0xa8] = "Darwin UFS",
[0xa9] = "NetBSD",
[0xab] = "Darwin boot",
[0xb7] = "BSDI fs",
[0xb8] = "BSDI swap",
[0xbb] = "Boot Wizard hidden",
[0xbe] = "Solaris boot",
[0xbf] = "Solaris",
[0xc1] = "DRDOS/sec (FAT-12)",
[0xc4] = "DRDOS/sec (FAT-16 < 32M)",
[0xc6] = "DRDOS/sec (FAT-16)",
[0xc7] = "Syrinx",
[0xda] = "Non-FS data",
[0xdb] = "CP/M / CTOS / ...",
[0xde] = "Dell Utility",
[0xdf] = "BootIt",
[0xe1] = "DOS access",
[0xe3] = "DOS R/O",
[0xe4] = "SpeedStor",
[0xeb] = "BeOS fs",
[0xee] = "EFI GPT",
[0xef] = "EFI (FAT-12/16/32)",
[0xf0] = "Linux/PA-RISC boot",
[0xf1] = "SpeedStor",
[0xf4] = "SpeedStor",
[0xf2] = "DOS secondary",
[0xfd] = "Linux raid autodetect",
[0xfe] = "LANstep",
[0xff] = "BBT",
};
return type_names[type] != NULL ? type_names[type] : "Unknown";
}
/* Reads sector SECTOR from partition P into BUFFER, which must
have room for BLOCK_SECTOR_SIZE bytes. */
static void
partition_read (void *p_, block_sector_t sector, void *buffer)
{
struct partition *p = p_;
block_read (p->block, p->start + sector, buffer);
}
/* Write sector SECTOR to partition P from BUFFER, which must
contain BLOCK_SECTOR_SIZE bytes. Returns after the block has
acknowledged receiving the data. */
static void
partition_write (void *p_, block_sector_t sector, const void *buffer)
{
struct partition *p = p_;
block_write (p->block, p->start + sector, buffer);
}
static struct block_operations partition_operations =
{
partition_read,
partition_write
};

8
src/devices/partition.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef DEVICES_PARTITION_H
#define DEVICES_PARTITION_H
struct block;
void partition_scan (struct block *);
#endif /* devices/partition.h */

83
src/devices/pit.c Normal file
View File

@@ -0,0 +1,83 @@
#include "devices/pit.h"
#include <debug.h>
#include <stdint.h>
#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);
}

8
src/devices/pit.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef DEVICES_PIT_H
#define DEVICES_PIT_H
#include <stdint.h>
void pit_configure_channel (int channel, int mode, int frequency);
#endif /* devices/pit.h */

112
src/devices/rtc.c Normal file
View File

@@ -0,0 +1,112 @@
#include "devices/rtc.h"
#include <stdio.h>
#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);
}

8
src/devices/rtc.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef RTC_H
#define RTC_H
typedef unsigned long time_t;
time_t rtc_get_time (void);
#endif

228
src/devices/serial.c Normal file
View File

@@ -0,0 +1,228 @@
#include "devices/serial.h"
#include <debug.h>
#include "devices/input.h"
#include "devices/intq.h"
#include "devices/timer.h"
#include "threads/io.h"
#include "threads/interrupt.h"
#include "threads/synch.h"
#include "threads/thread.h"
/* Register definitions for the 16550A UART used in PCs.
The 16550A has a lot more going on than shown here, but this
is all we need.
Refer to [PC16650D] for hardware information. */
/* I/O port base address for the first serial port. */
#define IO_BASE 0x3f8
/* 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.. */
/* 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 LCR_N81 0x03 /* No parity, 8 data bits, 1 stop bit. */
#define LCR_DLAB 0x80 /* Divisor Latch Access Bit (DLAB). */
/* MODEM Control Register. */
#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. */
/* Transmission mode. */
static enum { UNINIT, POLL, QUEUE } mode;
/* Data to be transmitted. */
static struct intq txq;
static void set_serial (int bps);
static void putc_poll (uint8_t);
static void write_ier (void);
static intr_handler_func serial_interrupt;
/* Initializes the serial port device for polling mode.
Polling mode busy-waits for the serial port to become free
before writing to it. It's slow, but until interrupts have
been initialized it's all we can do. */
static void
init_poll (void)
{
ASSERT (mode == UNINIT);
outb (IER_REG, 0); /* Turn off all interrupts. */
outb (FCR_REG, 0); /* Disable FIFO. */
set_serial (9600); /* 9.6 kbps, N-8-1. */
outb (MCR_REG, MCR_OUT2); /* Required to enable interrupts. */
intq_init (&txq);
mode = POLL;
}
/* Initializes the serial port device for queued interrupt-driven
I/O. With interrupt-driven I/O we don't waste CPU time
waiting for the serial device to become ready. */
void
serial_init_queue (void)
{
enum intr_level old_level;
if (mode == UNINIT)
init_poll ();
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. */
void
serial_putc (uint8_t byte)
{
enum intr_level old_level = intr_disable ();
if (mode != QUEUE)
{
/* If we're not set up for interrupt-driven I/O yet,
use dumb polling to transmit a byte. */
if (mode == UNINIT)
init_poll ();
putc_poll (byte);
}
else
{
/* Otherwise, queue a byte and update the interrupt enable
register. */
if (old_level == INTR_OFF && intq_full (&txq))
{
/* Interrupts are off and the transmit queue is full.
If we wanted to wait for the queue to empty,
we'd have to reenable interrupts.
That's impolite, so we'll send a character via
polling instead. */
putc_poll (intq_getc (&txq));
}
intq_putc (&txq, byte);
write_ier ();
}
intr_set_level (old_level);
}
/* Flushes anything in the serial buffer out the port in polling
mode. */
void
serial_flush (void)
{
enum intr_level old_level = intr_disable ();
while (!intq_empty (&txq))
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 ();
}
/* Configures the serial port for BPS bits per second. */
static void
set_serial (int bps)
{
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 data rate. */
outb (LS_REG, divisor & 0xff);
outb (MS_REG, divisor >> 8);
/* Reset DLAB. */
outb (LCR_REG, LCR_N81);
}
/* Update interrupt enable register. */
static void
write_ier (void)
{
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,
and then transmits BYTE. */
static void
putc_poll (uint8_t byte)
{
ASSERT (intr_get_level () == INTR_OFF);
while ((inb (LSR_REG) & LSR_THRE) == 0)
continue;
outb (THR_REG, byte);
}
/* 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 ();
}

11
src/devices/serial.h Normal file
View File

@@ -0,0 +1,11 @@
#ifndef DEVICES_SERIAL_H
#define DEVICES_SERIAL_H
#include <stdint.h>
void serial_init_queue (void);
void serial_putc (uint8_t);
void serial_flush (void);
void serial_notify (void);
#endif /* devices/serial.h */

133
src/devices/shutdown.c Normal file
View File

@@ -0,0 +1,133 @@
#include "devices/shutdown.h"
#include <console.h>
#include <stdio.h>
#include "devices/kbd.h"
#include "devices/serial.h"
#include "devices/timer.h"
#include "threads/io.h"
#include "threads/thread.h"
#ifdef USERPROG
#include "userprog/exception.h"
#endif
#ifdef FILESYS
#include "devices/block.h"
#include "filesys/filesys.h"
#endif
/* Keyboard control register port. */
#define CONTROL_REG 0x64
/* How to shut down when shutdown() is called. */
static enum shutdown_type how = SHUTDOWN_NONE;
static void print_stats (void);
/* Shuts down the machine in the way configured by
shutdown_configure(). If the shutdown type is SHUTDOWN_NONE
(which is the default), returns without doing anything. */
void
shutdown (void)
{
switch (how)
{
case SHUTDOWN_POWER_OFF:
shutdown_power_off ();
break;
case SHUTDOWN_REBOOT:
shutdown_reboot ();
break;
default:
/* Nothing to do. */
break;
}
}
/* Sets TYPE as the way that machine will shut down when PintOS
execution is complete. */
void
shutdown_configure (enum shutdown_type type)
{
how = type;
}
/* Reboots the machine via the keyboard controller. */
void
shutdown_reboot (void)
{
printf ("Rebooting...\n");
/* See [kbd] for details on how to program the keyboard
* controller. */
for (;;)
{
int i;
/* Poll keyboard controller's status byte until
* 'input buffer empty' is reported. */
for (i = 0; i < 0x10000; i++)
{
if ((inb (CONTROL_REG) & 0x02) == 0)
break;
timer_udelay (2);
}
timer_udelay (50);
/* Pulse bit 0 of the output port P2 of the keyboard controller.
* This will reset the CPU. */
outb (CONTROL_REG, 0xfe);
timer_udelay (50);
}
}
/* Powers down the machine we're running on,
as long as we're running on Bochs or QEMU. */
void
shutdown_power_off (void)
{
const char s[] = "Shutdown";
const char *p;
#ifdef FILESYS
filesys_done ();
#endif
print_stats ();
printf ("Powering off...\n");
serial_flush ();
/* This is a special power-off sequence supported by Bochs and
QEMU, but not by physical hardware. */
for (p = s; *p != '\0'; p++){
outb (0x8900, *p);
}
outw (0x604, 0x00 | 0x2000);
/* This will power off a VMware VM if "gui.exitOnCLIHLT = TRUE"
is set in its configuration file. (The "pintos" script does
that automatically.) */
asm volatile ("cli; hlt" : : : "memory");
/* None of those worked. */
printf ("still running...\n");
for (;;);
}
/* Print statistics about PintOS execution. */
static void
print_stats (void)
{
timer_print_stats ();
thread_print_stats ();
#ifdef FILESYS
block_print_stats ();
#endif
console_print_stats ();
kbd_print_stats ();
#ifdef USERPROG
exception_print_stats ();
#endif
}

19
src/devices/shutdown.h Normal file
View File

@@ -0,0 +1,19 @@
#ifndef DEVICES_SHUTDOWN_H
#define DEVICES_SHUTDOWN_H
#include <debug.h>
/* How to shut down when PintOS has nothing left to do. */
enum shutdown_type
{
SHUTDOWN_NONE, /* Loop forever. */
SHUTDOWN_POWER_OFF, /* Power off the machine (if possible). */
SHUTDOWN_REBOOT, /* Reboot the machine (if possible). */
};
void shutdown (void);
void shutdown_configure (enum shutdown_type);
void shutdown_reboot (void) NO_RETURN;
void shutdown_power_off (void) NO_RETURN;
#endif /* devices/shutdown.h */

68
src/devices/speaker.c Normal file
View File

@@ -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 ();
}
}

8
src/devices/speaker.h Normal file
View File

@@ -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 */

82
src/devices/swap.c Normal file
View File

@@ -0,0 +1,82 @@
#include "devices/swap.h"
#include "devices/block.h"
#include "threads/synch.h"
#include "threads/vaddr.h"
#include <bitmap.h>
#include <debug.h>
#include <stdio.h>
/* Pointer to the swap device */
static struct block *swap_device;
/* Pointer to a bitmap to track used swap pages */
static struct bitmap *swap_bitmap;
/* Lock that protects swap_bitmap from unsynchronised access */
static struct lock swap_lock;
/* Number of sectors needed to store a page */
#define PAGE_SECTORS (PGSIZE / BLOCK_SECTOR_SIZE)
/* Sets up the swap space */
void
swap_init (void)
{
// locate the swap block allocated to the kernel
swap_device = block_get_role (BLOCK_SWAP);
if (swap_device == NULL) {
printf ("no swap device--swap disabled\n");
swap_bitmap = bitmap_create (0);
} else {
// create a bitmap with 1 slot per page-sized chunk of memory on the swap block
swap_bitmap = bitmap_create (block_size (swap_device) / PAGE_SECTORS);
}
if (swap_bitmap == NULL){
PANIC ("couldn't create swap bitmap");
}
lock_init (&swap_lock);
}
/* Swaps page at VADDR out of memory, returns the swap-slot used */
size_t
swap_out (const void *vaddr)
{
// find available swap-slot for the page to be swapped out
lock_acquire (&swap_lock);
size_t slot = bitmap_scan_and_flip (swap_bitmap, 0, 1, false);
lock_release (&swap_lock);
if (slot == BITMAP_ERROR)
return BITMAP_ERROR;
// calculate block sector from swap-slot number
size_t sector = slot * PAGE_SECTORS;
// loop over each sector of the page, copying it from memory into swap
for (size_t i = 0; i < PAGE_SECTORS; i++)
block_write (swap_device, sector + i, vaddr + i * BLOCK_SECTOR_SIZE);
return slot;
}
/* Swaps page on disk in swap-slot SLOT into memory at VADDR */
void
swap_in (void *vaddr, size_t slot)
{
// calculate block sector from swap-slot number
size_t sector = slot * PAGE_SECTORS;
// loop over each sector of the page, copying it from swap into memory
for (size_t i = 0; i < PAGE_SECTORS; i++)
block_read (swap_device, sector + i, vaddr + i * BLOCK_SECTOR_SIZE);
// clear the swap-slot previously used by this page
swap_drop (slot);
}
/* Clears the swap-slot SLOT so that it can be used for another page */
void
swap_drop (size_t slot)
{
bitmap_reset (swap_bitmap, slot);
}

11
src/devices/swap.h Normal file
View File

@@ -0,0 +1,11 @@
#ifndef DEVICES_SWAP_H
#define DEVICES_SWAP_H 1
#include <stddef.h>
void swap_init (void);
size_t swap_out (const void *vaddr);
void swap_in (void *vaddr, size_t slot);
void swap_drop (size_t slot);
#endif /* devices/swap.h */

246
src/devices/timer.c Normal file
View File

@@ -0,0 +1,246 @@
#include "devices/timer.h"
#include <debug.h>
#include <inttypes.h>
#include <round.h>
#include <stdio.h>
#include "devices/pit.h"
#include "threads/interrupt.h"
#include "threads/synch.h"
#include "threads/thread.h"
/* See [8254] for hardware details of the 8254 timer chip. */
#if TIMER_FREQ < 19
#error 8254 timer requires TIMER_FREQ >= 19
#endif
#if TIMER_FREQ > 1000
#error TIMER_FREQ <= 1000 recommended
#endif
/* Number of timer ticks since OS booted. */
static int64_t ticks;
/* Number of loops per timer tick.
Initialized by timer_calibrate(). */
static unsigned loops_per_tick;
static intr_handler_func timer_interrupt;
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);
static void real_time_delay (int64_t num, int32_t denom);
/* Sets up the timer to interrupt TIMER_FREQ times per second,
and registers the corresponding interrupt. */
void
timer_init (void)
{
pit_configure_channel (0, 2, TIMER_FREQ);
intr_register_ext (0x20, timer_interrupt, "8254 Timer");
}
/* Calibrates loops_per_tick, used to implement brief delays. */
void
timer_calibrate (void)
{
unsigned high_bit, test_bit;
ASSERT (intr_get_level () == INTR_ON);
printf ("Calibrating timer... ");
/* Approximate loops_per_tick as the largest power-of-two
still less than one timer tick. */
loops_per_tick = 1u << 10;
while (!too_many_loops (loops_per_tick << 1))
{
loops_per_tick <<= 1;
ASSERT (loops_per_tick != 0);
}
/* Refine the next 8 bits of loops_per_tick. */
high_bit = loops_per_tick;
for (test_bit = high_bit >> 1; test_bit != high_bit >> 10; test_bit >>= 1)
if (!too_many_loops (high_bit | test_bit))
loops_per_tick |= test_bit;
printf ("%'"PRIu64" loops/s.\n", (uint64_t) loops_per_tick * TIMER_FREQ);
}
/* Returns the number of timer ticks since the OS booted. */
int64_t
timer_ticks (void)
{
enum intr_level old_level = intr_disable ();
int64_t t = ticks;
intr_set_level (old_level);
return t;
}
/* Returns the number of timer ticks elapsed since THEN, which
should be a value once returned by timer_ticks(). */
int64_t
timer_elapsed (int64_t then)
{
return timer_ticks () - then;
}
/* Sleeps for approximately TICKS timer ticks. Interrupts must
be turned on. */
void
timer_sleep (int64_t ticks)
{
int64_t start = timer_ticks ();
ASSERT (intr_get_level () == INTR_ON);
while (timer_elapsed (start) < ticks)
thread_yield ();
}
/* Sleeps for approximately MS milliseconds. Interrupts must be
turned on. */
void
timer_msleep (int64_t ms)
{
real_time_sleep (ms, 1000);
}
/* Sleeps for approximately US microseconds. Interrupts must be
turned on. */
void
timer_usleep (int64_t us)
{
real_time_sleep (us, 1000 * 1000);
}
/* Sleeps for approximately NS nanoseconds. Interrupts must be
turned on. */
void
timer_nsleep (int64_t ns)
{
real_time_sleep (ns, 1000 * 1000 * 1000);
}
/* Busy-waits for approximately MS milliseconds. Interrupts need
not be turned on.
Busy waiting wastes CPU cycles, and busy waiting with
interrupts off for the interval between timer ticks or longer
will cause timer ticks to be lost. Thus, use timer_msleep()
instead if interrupts are enabled. */
void
timer_mdelay (int64_t ms)
{
real_time_delay (ms, 1000);
}
/* Sleeps for approximately US microseconds. Interrupts need not
be turned on.
Busy waiting wastes CPU cycles, and busy waiting with
interrupts off for the interval between timer ticks or longer
will cause timer ticks to be lost. Thus, use timer_usleep()
instead if interrupts are enabled. */
void
timer_udelay (int64_t us)
{
real_time_delay (us, 1000 * 1000);
}
/* Sleeps execution for approximately NS nanoseconds. Interrupts
need not be turned on.
Busy waiting wastes CPU cycles, and busy waiting with
interrupts off for the interval between timer ticks or longer
will cause timer ticks to be lost. Thus, use timer_nsleep()
instead if interrupts are enabled.*/
void
timer_ndelay (int64_t ns)
{
real_time_delay (ns, 1000 * 1000 * 1000);
}
/* Prints timer statistics. */
void
timer_print_stats (void)
{
printf ("Timer: %"PRId64" ticks\n", timer_ticks ());
}
/* Timer interrupt handler. */
static void
timer_interrupt (struct intr_frame *args UNUSED)
{
ticks++;
thread_tick ();
}
/* Returns true if LOOPS iterations waits for more than one timer
tick, otherwise false. */
static bool
too_many_loops (unsigned loops)
{
/* Wait for a timer tick. */
int64_t start = ticks;
while (ticks == start)
barrier ();
/* Run LOOPS loops. */
start = ticks;
busy_wait (loops);
/* If the tick count changed, we iterated too long. */
barrier ();
return start != ticks;
}
/* Iterates through a simple loop LOOPS times, for implementing
brief delays.
Marked NO_INLINE because code alignment can significantly
affect timings, so that if this function was inlined
differently in different places the results would be difficult
to predict. */
static void NO_INLINE
busy_wait (int64_t loops)
{
while (loops-- > 0)
barrier ();
}
/* Sleep for approximately NUM/DENOM seconds. */
static void
real_time_sleep (int64_t num, int32_t denom)
{
/* Convert NUM/DENOM seconds into timer ticks, rounding down.
(NUM / DENOM) s
---------------------- = NUM * TIMER_FREQ / DENOM ticks.
1 s / TIMER_FREQ ticks
*/
int64_t ticks = num * TIMER_FREQ / denom;
ASSERT (intr_get_level () == INTR_ON);
if (ticks > 0)
{
/* We're waiting for at least one full timer tick. Use
timer_sleep() because it will yield the CPU to other
processes. */
timer_sleep (ticks);
}
else
{
/* Otherwise, use a busy-wait loop for more accurate
sub-tick timing. */
real_time_delay (num, denom);
}
}
/* Busy-wait for approximately NUM/DENOM seconds. */
static void
real_time_delay (int64_t num, int32_t denom)
{
/* Scale the numerator and denominator down by 1000 to avoid
the possibility of overflow. */
ASSERT (denom % 1000 == 0);
busy_wait (loops_per_tick * num / 1000 * TIMER_FREQ / (denom / 1000));
}

29
src/devices/timer.h Normal file
View File

@@ -0,0 +1,29 @@
#ifndef DEVICES_TIMER_H
#define DEVICES_TIMER_H
#include <round.h>
#include <stdint.h>
/* Number of timer interrupts per second. */
#define TIMER_FREQ 100
void timer_init (void);
void timer_calibrate (void);
int64_t timer_ticks (void);
int64_t timer_elapsed (int64_t);
/* Sleep and yield the CPU to other threads. */
void timer_sleep (int64_t ticks);
void timer_msleep (int64_t milliseconds);
void timer_usleep (int64_t microseconds);
void timer_nsleep (int64_t nanoseconds);
/* Busy waits. */
void timer_mdelay (int64_t milliseconds);
void timer_udelay (int64_t microseconds);
void timer_ndelay (int64_t nanoseconds);
void timer_print_stats (void);
#endif /* devices/timer.h */

172
src/devices/vga.c Normal file
View File

@@ -0,0 +1,172 @@
#include "devices/vga.h"
#include <round.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "devices/speaker.h"
#include "threads/io.h"
#include "threads/interrupt.h"
#include "threads/vaddr.h"
/* VGA text screen support. See [FREEVGA] for more information. */
/* Number of columns and rows on the text display. */
#define COL_CNT 80
#define ROW_CNT 25
/* Current cursor position. (0,0) is in the upper left corner of
the display. */
static size_t cx, cy;
/* Attribute value for gray text on a black background. */
#define GRAY_ON_BLACK 0x07
/* Framebuffer. See [FREEVGA] under "VGA Text Mode Operation".
The character at (x,y) is fb[y][x][0].
The attribute at (x,y) is fb[y][x][1]. */
static uint8_t (*fb)[COL_CNT][2];
static void clear_row (size_t y);
static void cls (void);
static void newline (void);
static void move_cursor (void);
static void find_cursor (size_t *x, size_t *y);
/* Initializes the VGA text display. */
static void
init (void)
{
/* Already initialized? */
static bool inited;
if (!inited)
{
fb = ptov (0xb8000);
find_cursor (&cx, &cy);
inited = true;
}
}
/* Writes C to the VGA text display, interpreting control
characters in the conventional ways. */
void
vga_putc (int c)
{
/* Disable interrupts to lock out interrupt handlers
that might write to the console. */
enum intr_level old_level = intr_disable ();
init ();
switch (c)
{
case '\n':
newline ();
break;
case '\f':
cls ();
break;
case '\b':
if (cx > 0)
cx--;
break;
case '\r':
cx = 0;
break;
case '\t':
cx = ROUND_UP (cx + 1, 8);
if (cx >= COL_CNT)
newline ();
break;
case '\a':
intr_set_level (old_level);
speaker_beep ();
intr_disable ();
break;
default:
fb[cy][cx][0] = c;
fb[cy][cx][1] = GRAY_ON_BLACK;
if (++cx >= COL_CNT)
newline ();
break;
}
/* Update cursor position. */
move_cursor ();
intr_set_level (old_level);
}
/* Clears the screen and moves the cursor to the upper left. */
static void
cls (void)
{
size_t y;
for (y = 0; y < ROW_CNT; y++)
clear_row (y);
cx = cy = 0;
move_cursor ();
}
/* Clears row Y to spaces. */
static void
clear_row (size_t y)
{
size_t x;
for (x = 0; x < COL_CNT; x++)
{
fb[y][x][0] = ' ';
fb[y][x][1] = GRAY_ON_BLACK;
}
}
/* Advances the cursor to the first column in the next line on
the screen. If the cursor is already on the last line on the
screen, scrolls the screen upward one line. */
static void
newline (void)
{
cx = 0;
cy++;
if (cy >= ROW_CNT)
{
cy = ROW_CNT - 1;
memmove (&fb[0], &fb[1], sizeof fb[0] * (ROW_CNT - 1));
clear_row (ROW_CNT - 1);
}
}
/* Moves the hardware cursor to (cx,cy). */
static void
move_cursor (void)
{
/* See [FREEVGA] under "Manipulating the Text-mode Cursor". */
uint16_t cp = cx + COL_CNT * cy;
outw (0x3d4, 0x0e | (cp & 0xff00));
outw (0x3d4, 0x0f | (cp << 8));
}
/* Reads the current hardware cursor position into (*X,*Y). */
static void
find_cursor (size_t *x, size_t *y)
{
/* See [FREEVGA] under "Manipulating the Text-mode Cursor". */
uint16_t cp;
outb (0x3d4, 0x0e);
cp = inb (0x3d5) << 8;
outb (0x3d4, 0x0f);
cp |= inb (0x3d5);
*x = cp % COL_CNT;
*y = cp / COL_CNT;
}

6
src/devices/vga.h Normal file
View File

@@ -0,0 +1,6 @@
#ifndef DEVICES_VGA_H
#define DEVICES_VGA_H
void vga_putc (int);
#endif /* devices/vga.h */