summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--kernel/drivers/ata.c58
-rw-r--r--kernel/include/comus/drivers/ata.h1
-rw-r--r--kernel/include/comus/procs.h5
-rw-r--r--kernel/include/comus/syscalls.h5
-rw-r--r--kernel/syscall.c99
-rw-r--r--user/forkman.c363
-rw-r--r--user/include/unistd.h40
-rw-r--r--user/lib/syscall.S3
8 files changed, 547 insertions, 27 deletions
diff --git a/kernel/drivers/ata.c b/kernel/drivers/ata.c
index 6f53d04..2fc059f 100644
--- a/kernel/drivers/ata.c
+++ b/kernel/drivers/ata.c
@@ -252,6 +252,7 @@ static enum ide_error ide_channel_poll(struct ide_channel *channel,
if (advanced_check) {
uint8_t state = ide_channel_read(channel, ATA_REG_STATUS);
+ // check for errors or faults
if (state & ATA_SR_ERROR) {
return IDE_ERROR_POLL_STATUS_REGISTER_ERROR;
}
@@ -260,7 +261,7 @@ static enum ide_error ide_channel_poll(struct ide_channel *channel,
return IDE_ERROR_POLL_DEVICE_FAULT;
}
- // BSY = 0; DF = 0; ERR = 0 so we should check for DRQ now.
+ // then check if drive is ready
if ((state & ATA_SR_DRIVEREQUESTREADY) == 0) {
return IDE_ERROR_POLL_DRIVE_REQUEST_NOT_READY;
}
@@ -322,7 +323,7 @@ static void ide_error_print(struct ide_device *dev, const enum ide_error err)
static void ide_initialize(uint32_t BAR0, uint32_t BAR1, uint32_t BAR2,
uint32_t BAR3, uint32_t BAR4)
{
- // 1- Detect I/O Ports which interface IDE Controller:
+ // calculate which io ports interface with the IDE controller
ide_channels[ATA_PRIMARY] = (struct ide_channel){
.io_base = (BAR0 & 0xFFFFFFFC) + 0x1F0 * (!BAR0),
.control_base = (BAR1 & 0xFFFFFFFC) + 0x3F6 * (!BAR1),
@@ -334,11 +335,13 @@ static void ide_initialize(uint32_t BAR0, uint32_t BAR1, uint32_t BAR2,
.bus_master_ide_base = (BAR4 & 0xFFFFFFFC) + 8,
};
- // 2- Disable IRQs:
+ // disable irqs
ide_channel_write(channel(ATA_PRIMARY), ATA_REG_CONTROL, 2);
ide_channel_write(channel(ATA_SECONDARY), ATA_REG_CONTROL, 2);
- // 3- Detect ATA-ATAPI Devices:
+ // detect disks by writing CMD_IDENTIFY to each one and checking for err.
+ // if device exists, ask for its ID space and copy out info about the
+ // device into the ide_device struct
uint32_t device_count = 0;
for (uint8_t channel_idx = 0; channel_idx < 2; channel_idx++) {
// drive idx is like device_count but it starts at 0 per channel
@@ -362,7 +365,7 @@ static void ide_initialize(uint32_t BAR0, uint32_t BAR1, uint32_t BAR2,
kspin_milliseconds(1);
if (ide_channel_read(chan, ATA_REG_STATUS) == 0) {
- continue; // if status == 0, no device.
+ continue; // if status == 0, no device
}
bool ata_err = false;
@@ -383,7 +386,7 @@ static void ide_initialize(uint32_t BAR0, uint32_t BAR1, uint32_t BAR2,
}
}
- // probe for ATAPI devices, if needed
+ // probe for ATAPI devices, though they aren't implemented
uint8_t type = IDE_ATA;
if (ata_err) {
uint8_t cl = ide_channel_read(chan, ATA_REG_LBA1);
@@ -396,7 +399,7 @@ static void ide_initialize(uint32_t BAR0, uint32_t BAR1, uint32_t BAR2,
WARN("ATAPI device found but ATAPI is not supported");
type = IDE_ATAPI;
} else {
- // unknown type (may not be a device).
+ // unknown type (may not be a device?)
continue;
}
@@ -425,16 +428,16 @@ static void ide_initialize(uint32_t BAR0, uint32_t BAR1, uint32_t BAR2,
// get size (depends on address mode):
if (dev->supported_command_sets & (1 << 26)) {
- // device uses 48-bit addressing:
+ // lba48
dev->size_in_sectors =
*((uint32_t *)(id_space_buf + ATA_IDENT_MAX_LBA_EXT));
} else {
- // device uses CHS or 28-bit dddressing:
+ // lba28
dev->size_in_sectors =
*((uint32_t *)(id_space_buf + ATA_IDENT_MAX_LBA));
}
- // string indicates model of device (Western Digital HDD, SONY DVD-RW...)
+ // string indicates model of device like "Western Digital HDD" etc
for (uint8_t i = 0; i < 40; i += 2) {
dev->model_str[i] = id_space_buf[ATA_IDENT_MODEL + i + 1];
dev->model_str[i + 1] = id_space_buf[ATA_IDENT_MODEL + i];
@@ -460,13 +463,13 @@ enum access_mode {
static uint8_t get_ata_cmd_for_access(enum lba_mode lba_mode,
enum access_mode mode)
{
- // routine that is followed:
- // If ( DMA & LBA48) DO_DMA_EXT;
- // If ( DMA & LBA28) DO_DMA_LBA;
- // If ( DMA & LBA28) DO_DMA_CHS;
- // If (!DMA & LBA48) DO_PIO_EXT;
- // If (!DMA & LBA28) DO_PIO_LBA;
- // If (!DMA & !LBA#) DO_PIO_CHS;
+ // outline of the algorithm:
+ // If ( dma & lba48) DO_DMA_EXT;
+ // If ( dma & lba28) DO_DMA_LBA;
+ // If ( dma & lba28) DO_DMA_CHS;
+ // If (!dma & lba48) DO_PIO_EXT;
+ // If (!dma & lba28) DO_PIO_LBA;
+ // If (!dma & !lba#) DO_PIO_CHS;
if (mode == READ) {
switch (lba_mode) {
@@ -500,7 +503,7 @@ static uint8_t get_ata_cmd_for_access(enum lba_mode lba_mode,
// if (lba_mode == 2 && dma == 1 && direction == 1)
// cmd = ATA_CMD_WRITE_DMA_EXT;
}
- // NOTE: unreachable
+ panic("unreachable");
return -1;
}
@@ -520,7 +523,8 @@ static enum ide_error ide_device_ata_access(struct ide_device *dev,
ide_channel_write(chan, ATA_REG_CONTROL,
chan->no_interrupt = (ide_irq_invoked = 0x0) + 0x02);
- // select one from LBA28, LBA48 or CHS
+ // select one from lba28, lba48 or CHS, and fill lba_io with the parameters
+ // for the disk access command
if (lba >= 0x10000000 || numsects > UINT8_MAX) {
// drive should support LBA in this case, or you are giving a bad LBA
lba_mode = LBA48;
@@ -561,7 +565,7 @@ static enum ide_error ide_device_ata_access(struct ide_device *dev,
if (dev->drive_idx > 1)
panic("unexpected drive_idx");
- // select Drive from the controller
+ // select our drive
if (lba_mode == CHS) {
ide_channel_write(chan, ATA_REG_HDDEVSEL,
0xA0 | (dev->drive_idx << 4) | head);
@@ -572,7 +576,7 @@ static enum ide_error ide_device_ata_access(struct ide_device *dev,
ide_channel_write(chan, ATA_REG_HDDEVSEL, 0x40 | (dev->drive_idx << 4));
}
- // write Parameters
+ // actually write the parameters
if (lba_mode == LBA48) {
ide_channel_write(chan, ATA_REG_SECCOUNT1, (numsects >> 8) & 0xff);
ide_channel_write(chan, ATA_REG_LBA3, lba_io[3]);
@@ -589,7 +593,7 @@ static enum ide_error ide_device_ata_access(struct ide_device *dev,
// TODO: if (dma) { ... } else {
if (mode == READ) {
- // PIO read
+ // just read all the bytes of the sectors out of the io port
for (size_t i = 0; i < numsects; i++) {
enum ide_error ret = ide_channel_poll(chan, 1);
if (ret)
@@ -599,7 +603,7 @@ static enum ide_error ide_device_ata_access(struct ide_device *dev,
rep_inw(chan->io_base, &buf[i * 256], 256);
}
} else {
- // PIO write
+ // just write all the bytes of the sectors into the io port
for (size_t i = 0; i < numsects; i++) {
enum ide_error err = ide_channel_poll(chan, 0);
#if LOG_LEVEL >= LOG_LVL_WARN
@@ -610,13 +614,15 @@ static enum ide_error ide_device_ata_access(struct ide_device *dev,
#endif
rep_outw(chan->io_base, &buf[i * 256], 256);
}
+
+ // flush the cache to fully complete the write
ide_channel_write(chan, ATA_REG_COMMAND,
(uint8_t[]){ ATA_CMD_CACHE_FLUSH, ATA_CMD_CACHE_FLUSH,
ATA_CMD_CACHE_FLUSH_EXT }[lba_mode]);
enum ide_error err = ide_channel_poll(chan, 0);
#if LOG_LEVEL >= LOG_LVL_WARN
if (err) {
- WARN("DRIVE WRITE FAILED:");
+ WARN("DRIVE WRITE FAILED, CACHE FLUSH ERR:");
ide_error_print(dev, err);
}
#endif
@@ -647,7 +653,9 @@ enum ide_error ide_device_read_sectors(ide_device_t dev_identifier,
// for (i = 0; i < numsects; i++)
// err = ide_atapi_read(drive, lba + i, 1, es, edi + (i*2048));
//panic("atapi unimplemented- todo");
- return 1;
+
+ // soft error instead of panic
+ return IDE_ERROR_UNIMPLEMENTED;
}
if (err) {
diff --git a/kernel/include/comus/drivers/ata.h b/kernel/include/comus/drivers/ata.h
index c404d84..c9ef207 100644
--- a/kernel/include/comus/drivers/ata.h
+++ b/kernel/include/comus/drivers/ata.h
@@ -27,6 +27,7 @@ enum ide_error {
IDE_ERROR_POLL_DEVICE_FAULT,
IDE_ERROR_POLL_STATUS_REGISTER_ERROR,
IDE_ERROR_POLL_WRITE_PROTECTED,
+ IDE_ERROR_UNIMPLEMENTED,
};
struct ide_devicelist {
diff --git a/kernel/include/comus/procs.h b/kernel/include/comus/procs.h
index 3df31c3..ee723aa 100644
--- a/kernel/include/comus/procs.h
+++ b/kernel/include/comus/procs.h
@@ -75,6 +75,11 @@ struct pcb {
uint64_t syscall;
uint64_t wakeup;
uint8_t exit_status;
+
+ // pipe to check for shared memory
+ void* shared_mem;
+ size_t shared_mem_pages;
+ pid_t shared_mem_source;
};
/// ordering of pcb queues
diff --git a/kernel/include/comus/syscalls.h b/kernel/include/comus/syscalls.h
index 8b671c2..146bce1 100644
--- a/kernel/include/comus/syscalls.h
+++ b/kernel/include/comus/syscalls.h
@@ -31,9 +31,12 @@
#define SYS_drm 18
#define SYS_ticks 19
#define SYS_seek 20
+#define SYS_allocshared 21
+#define SYS_popsharedmem 22
+#define SYS_keypoll 23
// UPDATE THIS DEFINITION IF MORE SYSCALLS ARE ADDED!
-#define N_SYSCALLS 21
+#define N_SYSCALLS 24
// interrupt vector entry for system calls
#define VEC_SYSCALL 0x80
diff --git a/kernel/syscall.c b/kernel/syscall.c
index 44ebfa0..8ed7631 100644
--- a/kernel/syscall.c
+++ b/kernel/syscall.c
@@ -3,6 +3,7 @@
#include <comus/user.h>
#include <comus/cpu.h>
#include <comus/syscalls.h>
+#include <comus/input.h>
#include <comus/drivers/acpi.h>
#include <comus/drivers/gpu.h>
#include <comus/drivers/pit.h>
@@ -482,6 +483,99 @@ static int sys_ticks(void)
return 0;
}
+static int sys_popsharedmem(void)
+{
+ RET(void *, res_mem);
+ *res_mem = NULL;
+
+ if (pcb->shared_mem == NULL) {
+ return 1;
+ }
+
+ struct pcb *const sharer = pcb_find_pid(pcb->shared_mem_source);
+ if (sharer == NULL) {
+ // process died or something since sharing
+ pcb->shared_mem = NULL;
+ return 1;
+ }
+
+ void *result =
+ mem_mapaddr(pcb->memctx, mem_get_phys(sharer->memctx, pcb->shared_mem),
+ pcb->shared_mem, pcb->shared_mem_pages * PAGE_SIZE,
+ F_WRITEABLE | F_UNPRIVILEGED);
+
+ // if (!result) {
+ // alert the other process that we cannot get its allocation?
+ // mem_free_pages(pcb->memctx, alloced);
+ // return 1;
+ // }
+
+ *res_mem = result;
+
+ pcb->shared_mem = NULL;
+
+ return 0;
+}
+
+static int sys_allocshared(void)
+{
+ ARG1(size_t, num_pages);
+ ARG2(unsigned short, otherpid); // same as pid_t
+ RET(void *, res_mem);
+ *res_mem = NULL;
+ assert(sizeof(unsigned short) == sizeof(pid_t),
+ "out of date sys_memshare syscall, pid_t changed?");
+
+ if (otherpid == pcb->pid || otherpid == 0) {
+ return 1;
+ }
+
+ struct pcb *const otherpcb = pcb_find_pid(otherpid);
+ if (otherpcb == NULL) {
+ // no such target process exists
+ return 1;
+ }
+ if (otherpcb->shared_mem != NULL) {
+ // it has yet to consume the last allocshared given to it
+ return 1;
+ }
+
+ void *alloced =
+ mem_alloc_pages(pcb->memctx, num_pages, F_WRITEABLE | F_UNPRIVILEGED);
+
+ if (!alloced) {
+ return 1;
+ }
+
+ otherpcb->shared_mem = alloced;
+ otherpcb->shared_mem_source = pcb->pid;
+ otherpcb->shared_mem_pages = num_pages;
+
+ *res_mem = alloced;
+
+ return 0;
+}
+
+// NOTE: observes AND consumes the key event
+static int sys_keypoll(void)
+{
+ ARG1(struct keycode *, keyev);
+ RET(int, waspressed);
+
+ void *ouraddr = kmapuseraddr(pcb->memctx, keyev, sizeof(struct keycode));
+
+ if (keycode_pop(ouraddr)) {
+ kunmapaddr(ouraddr);
+ *waspressed = false;
+ return 0;
+ }
+
+ kunmapaddr(ouraddr);
+
+ *waspressed = true;
+ return 0;
+}
+
static int sys_seek(void)
{
RET(long int, ret);
@@ -498,6 +592,7 @@ static int sys_seek(void)
return 0;
}
+// clang-format off
static int (*syscall_tbl[N_SYSCALLS])(void) = {
[SYS_exit] = sys_exit, [SYS_waitpid] = sys_waitpid,
[SYS_fork] = sys_fork, [SYS_exec] = sys_exec,
@@ -509,8 +604,10 @@ static int (*syscall_tbl[N_SYSCALLS])(void) = {
[SYS_sleep] = sys_sleep, [SYS_brk] = sys_brk,
[SYS_sbrk] = sys_sbrk, [SYS_poweroff] = sys_poweroff,
[SYS_drm] = sys_drm, [SYS_ticks] = sys_ticks,
- [SYS_seek] = sys_seek,
+ [SYS_seek] = sys_seek, [SYS_allocshared] = sys_allocshared,
+ [SYS_popsharedmem] = sys_popsharedmem, [SYS_keypoll] = sys_keypoll,
};
+// clang-format on
void syscall_handler(void)
{
diff --git a/user/forkman.c b/user/forkman.c
new file mode 100644
index 0000000..538992b
--- /dev/null
+++ b/user/forkman.c
@@ -0,0 +1,363 @@
+/*
+ * FORKMAN: a platformer game with IPC
+ *
+ * This executable will fork. One process is responsible for writing to the
+ * screen and getting user input, the other is responsible for game logic.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <unistd.h>
+#include "../kernel/include/comus/keycodes.h"
+
+#define DBG
+
+#define GAME_WIDTH 480
+#define GAME_HEIGHT 360
+#define SHARED_PAGES 10
+#define PAGE_SIZE 4096
+#define TILE_WIDTH 16
+#define TILE_HEIGHT 16
+#define GAME_WIDTH_TILES (GAME_WIDTH / TILE_WIDTH)
+#define GAME_HEIGHT_TILES (GAME_HEIGHT / TILE_HEIGHT)
+#define PLAYER_WIDTH 10
+#define PLAYER_HEIGHT 10
+
+typedef struct {
+ double x;
+ double y;
+} vec;
+
+typedef struct {
+ uint32_t *mapped_memory;
+ int width;
+ int height;
+ int bpp;
+ int scale;
+ size_t size;
+} framebuffer;
+
+enum tile_type {
+ TILE_AIR,
+ TILE_SOLID,
+ TILE_GRATE,
+};
+
+typedef struct {
+ enum tile_type type;
+} tile;
+
+#define CLIENT_BIT (1)
+#define SERVER_BIT (2)
+#define CLIENT_WAITING_BIT (1)
+#define SERVER_WAITING_BIT (2)
+
+typedef struct {
+ volatile uint8_t inner;
+} spinlock;
+
+enum key_state {
+ KEY_STATE_PRESSED,
+ KEY_STATE_UNPRESSED,
+ // TODO: JUST_PRESSED and JUST_RELEASED
+};
+
+typedef struct {
+ volatile size_t frame;
+ volatile size_t dummy_counter;
+ volatile uint8_t client_barrier;
+ volatile uint8_t server_barrier;
+ volatile enum key_state key_status[255];
+ spinlock player_lock;
+ volatile vec player_pos;
+ volatile vec player_vel;
+ volatile tile tiles[GAME_HEIGHT_TILES * GAME_WIDTH_TILES];
+ volatile uint8_t mem[PAGE_SIZE * (SHARED_PAGES - 1)];
+} sharedmem;
+
+static int display_server_entry(sharedmem *);
+static int client_entry(sharedmem *);
+static void barrier_wait(sharedmem *, int isclient);
+static volatile tile *tile_at(sharedmem *, size_t x, size_t y);
+static int is_inbounds(size_t x, size_t y);
+static void spinlock_lock(volatile spinlock *lock, int isclient);
+static void spinlock_unlock(volatile spinlock *lock, int isclient);
+
+int main(void)
+{
+ // static_assert where are you :(
+ if (SHARED_PAGES * 4096 < sizeof(sharedmem)) {
+ fprintf(stderr, "bad memory configuration");
+ return 1;
+ }
+
+ int child = fork();
+ if (child < 0) {
+ fprintf(stderr, "fork failed!\n");
+ return 1;
+ }
+
+ if (child) {
+ sharedmem *shared = allocshared(SHARED_PAGES, child);
+ if (!shared) {
+ fprintf(stderr, "memory share failure\n");
+ return 1;
+ }
+
+ return display_server_entry(shared);
+ } else {
+ sharedmem *shared;
+
+ while (!(shared = popsharedmem()))
+ sleep(1);
+
+ return client_entry(shared);
+ }
+}
+
+static void set_pixel(framebuffer *fb, size_t x, size_t y,
+ int state) // state is 0 or 1
+{
+ const size_t idx = x + y * fb->width;
+#ifdef DBG
+ if (idx > fb->size) {
+ printf("overflow?\n");
+ exit(0);
+ }
+#endif
+ fb->mapped_memory[idx] = state * (uint32_t)-1;
+}
+
+static void draw_filled(framebuffer *fb, const size_t x, const size_t y,
+ int state)
+{
+ for (size_t rx = x * TILE_WIDTH; rx < (x + 1) * TILE_WIDTH; ++rx) {
+ for (size_t ry = y * TILE_HEIGHT; ry < (y + 1) * TILE_HEIGHT; ++ry) {
+ set_pixel(fb, rx, ry, state);
+ }
+ }
+}
+
+static void draw_grate(framebuffer *fb, const size_t x, const size_t y)
+{
+ for (size_t rx = x * TILE_WIDTH; rx < (x + 1) * TILE_WIDTH; ++rx) {
+ for (size_t ry = y * TILE_HEIGHT; ry < (y + 1) * TILE_HEIGHT; ++ry) {
+ int state;
+ if (x == y) {
+ state = 1;
+ } else {
+ state = 0;
+ }
+ set_pixel(fb, rx, ry, state);
+ }
+ }
+}
+
+static void draw_tiles(sharedmem *shared, framebuffer *fb)
+{
+ for (size_t x = 0; x < GAME_WIDTH_TILES; ++x) {
+ for (size_t y = 0; y < GAME_HEIGHT_TILES; ++y) {
+ volatile tile *tile = tile_at(shared, x, y);
+ switch (tile->type) {
+ case TILE_AIR:
+ draw_filled(fb, x, y, 0);
+ break;
+ case TILE_SOLID:
+ draw_filled(fb, x, y, 1);
+ break;
+ case TILE_GRATE:
+ draw_grate(fb, x, y);
+ break;
+ }
+ }
+ }
+}
+
+static void draw_player(framebuffer *fb, vec pos)
+{
+ for (size_t x = pos.x; x < ((size_t)pos.x + PLAYER_WIDTH); ++x) {
+ for (size_t y = pos.y; y < ((size_t)pos.y + PLAYER_WIDTH); ++y) {
+ set_pixel(fb, x, y, 1);
+ }
+ }
+}
+
+static void init_level(sharedmem *shared)
+{
+ for (size_t i = 0; i < GAME_WIDTH_TILES; ++i) {
+ tile_at(shared, i, 10)->type = TILE_GRATE;
+ }
+
+ shared->player_pos = (vec){ .x = 5 * TILE_WIDTH, .y = 5 * TILE_HEIGHT };
+}
+
+static size_t get_total_time(size_t tick_start) // arbitrary units
+{
+ // 60 is arbitrary pretend fps
+ return ((ticks() - tick_start) / (1000 / 60));
+}
+
+static int display_server_entry(sharedmem *shared)
+{
+ framebuffer fb;
+ if (drm((void **)&fb.mapped_memory, &fb.width, &fb.height, &fb.bpp)) {
+ fprintf(stderr, "Unable to map framebuffer, display server failing\n");
+ return 1;
+ }
+
+ fb.size = (fb.width * fb.height * fb.bpp) / 8;
+
+ barrier_wait(shared, 0);
+
+ while (1) {
+ struct keycode keycode;
+
+ if (keypoll(&keycode)) {
+ if (keycode.flags & KC_FLAG_KEY_DOWN) {
+ shared->key_status[(uint8_t)keycode.key] = KEY_STATE_PRESSED;
+ }
+ if (keycode.flags & KC_FLAG_KEY_UP) {
+ shared->key_status[(uint8_t)keycode.key] = KEY_STATE_UNPRESSED;
+ }
+ }
+
+ draw_tiles(shared, &fb);
+
+ spinlock_lock(&shared->player_lock, 0);
+ draw_player(&fb, shared->player_pos);
+ spinlock_unlock(&shared->player_lock, 0);
+ }
+
+ return 0;
+}
+
+static int client_entry(sharedmem *shared)
+{
+ init_level(shared);
+
+ size_t start_ticks = ticks();
+
+ double last_time = get_total_time(start_ticks);
+
+ barrier_wait(shared, 1);
+ do {
+ double time = get_total_time(start_ticks);
+ double delta_time = time - last_time;
+ spinlock_lock(&shared->player_lock, 1);
+
+ shared->player_vel.y -= 9.8 * delta_time;
+
+ // framerate dependent...
+ const vec drag = {
+ .x = shared->player_vel.x * 0.1,
+ .y = shared->player_vel.y * 0.1,
+ };
+ shared->player_vel.x -= drag.x;
+ shared->player_vel.y -= drag.y;
+
+ for (size_t i = 0; i < shared->player_vel.x; ++i) {
+ size_t x = shared->player_pos.x + 1;
+ size_t y = shared->player_pos.y + 1;
+ // boundscheck to consider outside tiles to be solid
+ if (is_inbounds(x, y) && tile_at(shared, x, y)->type != TILE_AIR) {
+ break;
+ }
+ shared->player_pos.x += 1;
+ shared->player_pos.y += 1;
+ }
+
+ if (shared->key_status[KEY_SPACE] == KEY_STATE_PRESSED) {
+ shared->player_vel.y = 10;
+ } else if (shared->key_status[KEY_B] == KEY_STATE_PRESSED) {
+ shared->player_vel.y = -10;
+ }
+ spinlock_unlock(&shared->player_lock, 1);
+
+ } while (1);
+
+ return 0;
+}
+
+static volatile tile *tile_at(sharedmem *shared, size_t x, size_t y)
+{
+#ifdef DBG
+ if (!is_inbounds(x, y)) {
+ printf("out of bounds");
+ exit(0);
+ }
+#endif
+ const size_t idx = x + (y * GAME_WIDTH_TILES);
+ return shared->tiles + idx;
+}
+
+static int is_inbounds(size_t x, size_t y)
+{
+ const size_t idx = x + (y * GAME_WIDTH_TILES);
+ return idx < (GAME_WIDTH_TILES * GAME_HEIGHT_TILES);
+}
+
+static void spinlock_lock(volatile spinlock *lock, int isclient)
+{
+ const uint8_t bit = isclient ? CLIENT_BIT : SERVER_BIT;
+ const uint8_t otherbit = isclient ? SERVER_BIT : CLIENT_BIT;
+
+ // wait for us to be the only waiter
+ while (1) {
+ if (lock->inner == 0) {
+ lock->inner |= bit;
+ // recover from the possibility that something happened between that
+ // if statement and the |= operation. check for other bits being set
+ if ((lock->inner ^ bit) != 0) {
+ // okay, somebody messed with something, undo what we did and
+ // keep waiting
+ lock->inner ^= bit;
+ continue;
+ }
+
+ // okay all good, we are the exclusive owner
+ break;
+ }
+ }
+
+#ifdef DBG
+ // when we own the lock, only our bit should be active and the other proc's
+ // bit should not be active
+ if (lock->inner & otherbit || !(lock->inner & bit)) {
+ printf("SPINLOCK BAD\n");
+ exit(1);
+ }
+#endif
+}
+
+static void spinlock_unlock(volatile spinlock *lock, int isclient)
+{
+ const uint8_t bit = isclient ? CLIENT_BIT : SERVER_BIT;
+
+ // assume our thread of execution (process) is the only one changing our
+ // bit. this can be screwed over if the other process lies about whether
+ // it is server or client
+ lock->inner ^= bit;
+}
+
+static void barrier_wait(sharedmem *shared, int isclient)
+{
+ if (isclient) {
+ if (shared->server_barrier) {
+ shared->server_barrier = 0;
+ } else {
+ shared->client_barrier = 1;
+ while (shared->client_barrier)
+ ;
+ }
+ } else {
+ if (shared->client_barrier) {
+ shared->client_barrier = 0;
+ } else {
+ shared->server_barrier = 1;
+ while (shared->server_barrier)
+ ;
+ }
+ }
+}
diff --git a/user/include/unistd.h b/user/include/unistd.h
index 4f582d2..c54cd20 100644
--- a/user/include/unistd.h
+++ b/user/include/unistd.h
@@ -15,6 +15,14 @@
/* System Call Definitions */
+// NOTE: needs to match kernel input.h
+struct keycode {
+ char key;
+ char flags;
+};
+
+typedef unsigned short pid_t;
+
enum {
S_SET = 0,
S_CUR = 1,
@@ -185,6 +193,38 @@ extern void *brk(const void *addr);
extern void *sbrk(intptr_t increment);
/**
+ * Allocate a number of pages shared with another PID. Does not map the pages
+ * into the other process's vtable until the other process calls popsharedmem().
+ *
+ * @param num_pages number of pages to allocate
+ * @param other_pid pid of other process
+ * @return pointer to the virtual address which will be accessible by both,
+ * after popsharedmem() is called.
+ */
+extern void *allocshared(size_t num_pages, int other_pid);
+
+/**
+ * Checks if another process has tried to share memory with us, and return it.
+ * No size information is returned, it is only guaranteed that there is at least
+ * one page in the shared allocation. To get around this, the sharer can write
+ * a size number to the start of the first page.
+ *
+ * @return page aligned pointer to the start of the shared pages, or NULL if no
+ * process has tried to share with us, or NULL if we the shared virtual address
+ * space is already occupied in the caller's pagetable.
+ */
+extern void *popsharedmem(void);
+
+/**
+ * Get the most recent key event, if there is one.
+ *
+ * @param poll the keycode to write out to
+ * @return 0 if there was no key event, in which case `poll` was not changed, or
+ * 1 if there was an event and the caller should read from `poll`.
+ */
+extern int keypoll(struct keycode *poll);
+
+/**
* Poweroff the system.
*
* @return 1 on failure
diff --git a/user/lib/syscall.S b/user/lib/syscall.S
index 9f7025e..c45f641 100644
--- a/user/lib/syscall.S
+++ b/user/lib/syscall.S
@@ -30,3 +30,6 @@ SYSCALL poweroff SYS_poweroff
SYSCALL drm SYS_drm
SYSCALL ticks SYS_ticks
SYSCALL seek SYS_seek
+SYSCALL allocshared SYS_allocshared
+SYSCALL popsharedmem SYS_popsharedmem
+SYSCALL keypoll SYS_keypoll