provided code
This commit is contained in:
3
src/devices/.gitignore
vendored
Normal file
3
src/devices/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
build
|
||||
bochsrc.txt
|
||||
bochsout.txt
|
||||
7
src/devices/Make.vars
Normal file
7
src/devices/Make.vars
Normal 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
1
src/devices/Makefile
Normal file
@@ -0,0 +1 @@
|
||||
include ../Makefile.kernel
|
||||
223
src/devices/block.c
Normal file
223
src/devices/block.c
Normal 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
74
src/devices/block.h
Normal 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
527
src/devices/ide.c
Normal 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
6
src/devices/ide.h
Normal 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
52
src/devices/input.c
Normal 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
12
src/devices/input.h
Normal 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
114
src/devices/intq.c
Normal 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
43
src/devices/intq.h
Normal 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
213
src/devices/kbd.c
Normal 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
9
src/devices/kbd.h
Normal 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
324
src/devices/partition.c
Normal 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
8
src/devices/partition.h
Normal 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
83
src/devices/pit.c
Normal 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
8
src/devices/pit.h
Normal 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
112
src/devices/rtc.c
Normal 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
8
src/devices/rtc.h
Normal 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
228
src/devices/serial.c
Normal 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
11
src/devices/serial.h
Normal 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
133
src/devices/shutdown.c
Normal 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
19
src/devices/shutdown.h
Normal 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
68
src/devices/speaker.c
Normal 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
8
src/devices/speaker.h
Normal 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
82
src/devices/swap.c
Normal 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
11
src/devices/swap.h
Normal 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
246
src/devices/timer.c
Normal 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
29
src/devices/timer.h
Normal 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
172
src/devices/vga.c
Normal 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
6
src/devices/vga.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#ifndef DEVICES_VGA_H
|
||||
#define DEVICES_VGA_H
|
||||
|
||||
void vga_putc (int);
|
||||
|
||||
#endif /* devices/vga.h */
|
||||
Reference in New Issue
Block a user