From: John Tsiombikas Date: Fri, 13 Nov 2020 05:36:23 +0000 (+0200) Subject: display works sortof X-Git-Url: http://git.mutantstargoat.com/user/nuclear/?p=rpikern;a=commitdiff_plain;h=99bdc8838f189f79ba77bfbbf41d7f1b4839ce0f display works sortof --- diff --git a/Makefile b/Makefile index 24a3fb6..b4aa3a5 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ endif warn = -pedantic -Wall dbg = -g inc = -Isrc -Isrc/libc -gccopt = -marm -fpic -ffreestanding -nostdinc -ffast-math -fno-math-errno +gccopt = -marm -fno-pic -ffreestanding -nostdinc -ffast-math -fno-math-errno #arch = -mcpu=arm1176jzf-s arch = -mcpu=cortex-a7 @@ -26,7 +26,7 @@ CFLAGS = $(arch) $(warn) $(opt) $(dbg) $(gccopt) $(inc) $(def) ASFLAGS = $(arch) $(dbg) $(inc) LDFLAGS = -nostdlib -T rpikern.ld -print-gc-sections -QEMU_FLAGS = -m 1024 -M raspi2 -serial stdio -d guest_errors +QEMU_FLAGS = -vnc :0 -m 1024 -M raspi2 -serial stdio -d guest_errors $(bin): $(elf) diff --git a/doc/notes.md b/doc/notes.md index 0d4e804..5a372d8 100644 --- a/doc/notes.md +++ b/doc/notes.md @@ -1,6 +1,18 @@ Raspberry Pi2 notes =================== -The following is not all confirmed. +Accessing different peripherals migh return results out of order. See bcm2835 +spec section 1.3. Use memory barriers as follows: + - place write barrier before the first write to a peripheral + - place read barrier after the last read of a peripheral + +Multiple accesses to the same peripheral in a row do not require barriers. + +Interrupt handlers should have a read barrier before their first read, and end +with a write barrier. + +Cache clean and invalidate operations are also needed before the GPU can see our +command buffers... see: +https://github.com/rsta2/uspi/blob/38eaff4f715643a9/lib/synchronize.c Memory map ---------- diff --git a/src/main.c b/src/main.c index 29c0f14..8bab5a1 100644 --- a/src/main.c +++ b/src/main.c @@ -23,41 +23,47 @@ int main(void) static int cmdend; rpi_init(); - init_serial(115200); + /*init_serial(115200); done in rpi_init now for early debugging */ con_init(); printf("Detected raspberry pi %d, I/O base: %x\n", rpi_model, rpi_iobase); + printf("Main RAM base: %x, size: %u bytes\n", rpi_mem_base, rpi_mem_size); + printf("Video RAM base: %x, size: %u bytes\n", rpi_vmem_base, rpi_vmem_size); video_init(); printf("Going interactive\n"); for(;;) { - int c = getchar(); - - switch(c) { - case '\r': - case '\n': - if(!lastnl) { - ser_printstr("\r\n"); - cmdbuf[cmdend] = 0; - cmdend = 0; - cmdrun(cmdbuf); - } - lastnl = 1; - break; - - case -1: - lastnl = 0; - printf("error!\n"); - break; - - default: - lastnl = 0; - ser_putchar(c); - if(cmdend < sizeof cmdbuf) { - cmdbuf[cmdend++] = c; + while(ser_pending()) { + int c = getchar(); + + switch(c) { + case '\r': + case '\n': + if(!lastnl) { + printf("\r\n"); + cmdbuf[cmdend] = 0; + cmdend = 0; + cmdrun(cmdbuf); + } + lastnl = 1; + break; + + case -1: + lastnl = 0; + printf("error!\n"); + break; + + default: + lastnl = 0; + putchar(c); + if(cmdend < sizeof cmdbuf) { + cmdbuf[cmdend++] = c; + } } } + + /*video_update(1);*/ } return 0; @@ -71,6 +77,7 @@ void reboot(void) static void cmdrun(char *cmd) { + static int cur_x, cur_y; char *ptr, *args; while(*cmd && isspace(*cmd)) cmd++; @@ -81,6 +88,17 @@ static void cmdrun(char *cmd) if(strcmp(cmd, "reboot") == 0) { reboot(); + + } else if(strcmp(cmd, "down") == 0) { + printf("scroll down\n"); + cur_y += 10; + video_scroll(cur_x, cur_y); + + } else if(strcmp(cmd, "up") == 0) { + printf("scroll up\n"); + cur_y -= 10; + video_scroll(cur_x, cur_y); + } else if(strcmp(cmd, "help") == 0) { printf("help not implemented yet\n"); } else if(strcmp(cmd, "ver") == 0) { diff --git a/src/rpi.c b/src/rpi.c index 0b2f298..68f07e6 100644 --- a/src/rpi.c +++ b/src/rpi.c @@ -1,5 +1,10 @@ +#include +#include #include "rpi.h" +#include "sysctl.h" #include "asm.h" +#include "serial.h" +#include "debug.h" #define IOREG(offs) (*(volatile uint32_t*)(rpi_iobase | offs)) @@ -65,15 +70,60 @@ static int detect(void); int rpi_model; uint32_t rpi_iobase; -uint32_t rpi_memsize, rpi_vc_memsize; +uint32_t rpi_mem_base, rpi_mem_size, rpi_vmem_base, rpi_vmem_size; + + +/* needs to by 16-byte aligned, because the address we send over the mailbox + * interface, will have its 4 least significant bits masked off and taken over + * by the mailbox id + */ +static char propbuf[256] __attribute__((aligned(16))); +static struct rpi_prop *wrprop, *rdprop; + void rpi_init(void) { + struct rpi_prop *prop; + if((rpi_model = detect()) == -1) { for(;;) halt_cpu(); } - /* TODO */ + init_serial(115200); + + /* The model detected by detect is not accurate, get the correct board model + * through the mailbox property interface if possible. + * Also, detect the amount of CPU and GPU memory available. + */ + + rpi_prop(RPI_TAG_GETREV); + rpi_prop(RPI_TAG_GETRAM); + rpi_prop(RPI_TAG_GETVRAM); + if(rpi_prop_send() != -1) { + //hexdump(propbuf, sizeof propbuf); + + while((prop = rpi_prop_next())) { + switch(prop->id) { + case RPI_TAG_GETREV: + printf("board revision: %x\n", prop->data[0]); + /* TODO: guess rpi model based on board revision */ + break; + + case RPI_TAG_GETRAM: + rpi_mem_base = prop->data[0]; + rpi_mem_size = prop->data[1]; + break; + + case RPI_TAG_GETVRAM: + rpi_vmem_base = prop->data[0]; + rpi_vmem_size = prop->data[1]; + break; + + default: + break; + } + } + } } static int detect(void) @@ -97,6 +147,7 @@ static int detect(void) void rpi_reboot(void) { + mem_barrier(); PM_WDOG_REG = PM_PASSWD | 1; PM_RSTC_REG = PM_PASSWD | (PM_RSTC_REG & PMRSTC_WRCFG_CLEAR) | PMRSTC_WRCFG_FULL_RESET; for(;;) halt_cpu(); @@ -104,21 +155,143 @@ void rpi_reboot(void) void rpi_mbox_send(int chan, uint32_t msg) { + mem_barrier(); while(MBOX_STATUS_REG & MBOX_STAT_FULL); MBOX_WRITE_REG = (msg & 0xfffffff0) | chan; + mem_barrier(); } uint32_t rpi_mbox_recv(int chan) { uint32_t msg; + mem_barrier(); do { while(MBOX_STATUS_REG & MBOX_STAT_EMPTY); msg = MBOX_READ_REG; } while((msg & 0xf) != chan); + mem_barrier(); return msg & 0xfffffff0; } int rpi_mbox_pending(int chan) { + mem_barrier(); return (MBOX_STATUS_REG & MBOX_STAT_EMPTY) == 0; } + + +static struct { + int id, req_len, resp_len; +} taginfo[] = { + {RPI_TAG_GETMODEL, 0, 4}, + {RPI_TAG_GETREV, 0, 4}, + {RPI_TAG_GETRAM, 0, 8}, + {RPI_TAG_GETVRAM, 0, 8}, + {RPI_TAG_SETCLOCK, 4, 8}, + {RPI_TAG_ALLOCFB, 4, 8}, + {RPI_TAG_RELEASEFB, 0, 0}, + {RPI_TAG_BLANKSCR, 4, 4}, + {RPI_TAG_SETFBPHYS, 8, 8}, + {RPI_TAG_SETFBVIRT, 8, 8}, + {RPI_TAG_SETFBDEPTH, 4, 4}, + {RPI_TAG_GETFBPITCH, 0, 4}, + {RPI_TAG_SETFBOFFS, 8, 8}, + {RPI_TAG_GETFBOFFS, 0, 8}, + {0, 0, 0} +}; + +void rpi_prop(int id, ...) +{ + int i, req_len = 0, resp_len = 0; + va_list ap; + + if(!wrprop) { + wrprop = (struct rpi_prop*)(propbuf + sizeof(struct rpi_prop_header)); + } + + for(i=0; taginfo[i].id; i++) { + if(taginfo[i].id == id) { + req_len = taginfo[i].req_len; + resp_len = taginfo[i].resp_len; + break; + } + } + + wrprop->id = id; + wrprop->size = resp_len; + wrprop->res = 0; + + va_start(ap, id); + for(i=0; i> 2; i++) { + if(i < req_len >> 2) { + wrprop->data[i] = va_arg(ap, uint32_t); + } else { + wrprop->data[i] = 0; + } + } + va_end(ap); + + wrprop = RPI_PROP_NEXT(wrprop); +} + +int rpi_prop_send(void) +{ + struct rpi_prop_header *hdr = (struct rpi_prop_header*)propbuf; + uint32_t addr = (uint32_t)propbuf; + uint32_t size; + + /* terminate with null tag */ + wrprop->id = 0; + + size = (char*)wrprop - propbuf + 4; + + hdr->size = (size + 15) & 0xfffffff0; + hdr->res = 0; + + wrprop = rdprop = 0; + + /* clean and invalidate cache, otherwise VC will see stale data */ + sysctl_dcache_clean_inval(addr, hdr->size); + + rpi_mbox_send(RPI_MBOX_PROP, addr); + while(rpi_mbox_recv(RPI_MBOX_PROP) != addr); + + sysctl_dcache_clean_inval(addr, hdr->size); + + if(hdr->res != 0x80000000) { + printf("Property request failed: %x\n", hdr->res); + return -1; + } + return 0; +} + +struct rpi_prop *rpi_prop_next(void) +{ + struct rpi_prop *res; + if(!rdprop) { + rdprop = (struct rpi_prop*)(propbuf + sizeof(struct rpi_prop_header)); + } + + if(rdprop->id == 0) { + res = rdprop = 0; + } else { + res = rdprop; + rdprop->size = rdprop->res & 0x7fffffff; + rdprop = RPI_PROP_NEXT(rdprop); + } + return res; +} + +struct rpi_prop *rpi_prop_find(int id) +{ + struct rpi_prop *prop = (struct rpi_prop*)(propbuf + sizeof(struct rpi_prop_header)); + + while(prop->id) { + prop->size = prop->res & 0x7fffffff; + if(prop->id == id) { + return prop; + } + prop = RPI_PROP_NEXT(prop); + } + return 0; +} diff --git a/src/rpi.h b/src/rpi.h index 1ae7636..57a1cf7 100644 --- a/src/rpi.h +++ b/src/rpi.h @@ -12,6 +12,7 @@ #define RPI_MBOX_PROP 8 #define RPI_TAG_GETMODEL 0x010001 +#define RPI_TAG_GETREV 0x010002 #define RPI_TAG_GETRAM 0x010005 #define RPI_TAG_GETVRAM 0x010006 #define RPI_TAG_SETCLOCK 0x038002 @@ -22,6 +23,9 @@ #define RPI_TAG_SETFBPHYS 0x048003 #define RPI_TAG_SETFBVIRT 0x048004 #define RPI_TAG_SETFBDEPTH 0x048005 +#define RPI_TAG_GETFBPITCH 0x040008 +#define RPI_TAG_SETFBOFFS 0x048009 +#define RPI_TAG_GETFBOFFS 0x040009 struct rpi_prop_header { uint32_t size; @@ -38,7 +42,7 @@ struct rpi_prop { extern int rpi_model; extern uint32_t rpi_iobase; -extern uint32_t rpi_memsize, rpi_vc_memsize; +extern uint32_t rpi_mem_base, rpi_mem_size, rpi_vmem_base, rpi_vmem_size; void rpi_init(void); void rpi_reboot(void) __attribute__((noreturn)); @@ -47,4 +51,20 @@ void rpi_mbox_send(int chan, uint32_t msg); uint32_t rpi_mbox_recv(int chan); int rpi_mbox_pending(int chan); +/* usage: + * rpi_prop(RPI_TAG_WHATEVER, foo, bar); + * rpi_prop(RPI_TAG_XYZZY, 42); + * if(rpi_prop_send() != -1) { + * struct rpi_prop *prop; + * while((prop = rpi_prop_next())) { + * ... process response tags ... + * } + * } + */ +void rpi_prop(int id, ...); +int rpi_prop_send(void); +struct rpi_prop *rpi_prop_next(void); +/* find specific tag in response */ +struct rpi_prop *rpi_prop_find(int id); + #endif /* RPI_H_ */ diff --git a/src/serial.c b/src/serial.c index 1bc13fe..ba5faca 100644 --- a/src/serial.c +++ b/src/serial.c @@ -10,6 +10,7 @@ void init_serial(int baud) { uint32_t bdiv_fp6; + mem_barrier(); REG_CR = 0; /* disable UART */ /* disable pullups for GPIO 14 & 15 */ @@ -32,22 +33,32 @@ void init_serial(int baud) /* enable UART RX&TX */ REG_CR = CR_UARTEN | CR_TXEN | CR_RXEN; + mem_barrier(); } void ser_putchar(int c) { if(c == '\n') ser_putchar('\r'); + mem_barrier(); while(REG_FR & FR_TXFF); REG_DR = c & 0xff; + mem_barrier(); } int ser_getchar(void) { + mem_barrier(); while(REG_FR & FR_RXFE); return REG_DR & 0xff; } +int ser_pending(void) +{ + mem_barrier(); + return (REG_FR & FR_RXFE) == 0; +} + void ser_printstr(const char *s) { while(*s) { diff --git a/src/serial.h b/src/serial.h index 785457e..09c6bf9 100644 --- a/src/serial.h +++ b/src/serial.h @@ -6,6 +6,8 @@ void init_serial(int baud); void ser_putchar(int c); int ser_getchar(void); +int ser_pending(void); + void ser_printstr(const char *s); #endif /* SERIAL_H_ */ diff --git a/src/startup.s b/src/startup.s index 7765ede..3feac3d 100644 --- a/src/startup.s +++ b/src/startup.s @@ -28,19 +28,4 @@ startup: exit: wfe b exit - .global dbgled -dbgled: - ldr r3, =0x3f200000 @ gpio base - ldr r2, =0x9000 @ gpio 24 and 25 -> output - str r2, [r3, #8] @ store to GPFSEL2 - ldr r2, =0x01000000 @ bit 24 - tst r0, #1 - strne r2, [r3, #0x1c] @ GPSET0 - streq r2, [r3, #0x28] @ GPCLR0 - lsl r2, #1 - tst r0, #2 - strne r2, [r3, #0x1c] @ GPSET0 - streq r2, [r3, #0x28] @ GPCLR0 - bx lr - @ vi:set filetype=armasm: diff --git a/src/sysctl.S b/src/sysctl.S new file mode 100644 index 0000000..8112d7b --- /dev/null +++ b/src/sysctl.S @@ -0,0 +1,25 @@ + .text + .code 32 + +@ wait for interrupt +#define C7_CRM_WFI c0 +#define C7_OP2_WFI 4 +@ clean/inval operations +#define C7_CRM_I_INVAL c5 @ invalidate instruction cache +#define C7_CRM_D_INVAL c6 @ invalidate data cache +#define C7_CRM_INVAL c7 @ invalidate both (reg = 0) +#define C7_OP2_INVAL 0 +#define C7_CRM_D_CLEAN c10 @ clean data cache +#define C7_CRM_D_CLEAN_INVAL c14 @ clean and invalidate data cache +@ OP2 for cache clean/inval operations +#define C7_OP2_ALL 0 @ reg = 0 +#define C7_OP2_ADDR 1 +#define C7_OP2_IDX 2 +@ data synchronization barrier +#define C7_CRM_DSB c10 +#define C7_OP2_DSB 4 +@ data memory barrier +#define C7_CRM_DMB c10 +#define C7_OP2_DMB 5 + +@ vi:set filetype=armasm: diff --git a/src/sysctl.h b/src/sysctl.h new file mode 100644 index 0000000..8d9634a --- /dev/null +++ b/src/sysctl.h @@ -0,0 +1,18 @@ +#ifndef SYSCTL_H_ +#define SYSCTL_H_ + +/* c7 reg, op1=0, mode c14 is clean&inval, op2=1 is cacheline MVA */ +#define sysctl_dcache_clean_inval(addr, len) \ + do { \ + register uint32_t a asm("r0") = addr; \ + asm volatile( \ + "\n0:\tmcr p15, 0, %0, c7, c14, 1" \ + "\n\tadd %0, #64" \ + "\n\tcmp %0, %1" \ + "\n\tblo 0b" \ + :: "r"(a), "r"(addr + len) \ + : "memory"); \ + } while(0) + + +#endif /* SYSCTL_H_ */ diff --git a/src/video.c b/src/video.c index cd3533a..9f56e9c 100644 --- a/src/video.c +++ b/src/video.c @@ -6,100 +6,64 @@ #include "video.h" #include "mem.h" #include "debug.h" - -/* needs to by 16-byte aligned, because the address we send over the mailbox - * interface, will have its 4 least significant bits masked off and taken over - * by the mailbox id - */ -static char propbuf[256] __attribute__((aligned(16))); +#include "sysctl.h" static void *fb_pixels; -static int fb_width, fb_height, fb_depth, fb_size; +static int scr_width, scr_height; +static int fb_width, fb_height, fb_depth, fb_size, fb_pitch; +static int fb_xoffs, fb_yoffs; int video_init(void) { - int i; - struct rpi_prop_header *hdr = (struct rpi_prop_header*)propbuf; - struct rpi_prop *prop = (struct rpi_prop*)(hdr + 1); - - hdr->size = sizeof propbuf; - hdr->res = 0; - - fb_width = 1024; - fb_height = 600; + int i, j; + struct rpi_prop *prop; + uint32_t *fbptr; + + scr_width = 1024; + scr_height = 576; + /*fb_width = 1920; + fb_height = 1024;*/ + fb_width = scr_width; + fb_height = scr_height; fb_depth = 32; - prop->id = RPI_TAG_SETFBPHYS; - prop->size = 8; - prop->res = 0; - prop->data[0] = fb_width; - prop->data[1] = fb_height; - prop = RPI_PROP_NEXT(prop); - - prop->id = RPI_TAG_SETFBVIRT; - prop->size = 8; - prop->res = 0; - prop->data[0] = fb_width; - prop->data[1] = fb_height; - prop = RPI_PROP_NEXT(prop); - - prop->id = RPI_TAG_SETFBDEPTH; - prop->size = 4; - prop->res = 0; - prop->data[0] = fb_depth; - prop = RPI_PROP_NEXT(prop); - - prop->id = RPI_TAG_ALLOCFB; - prop->size = 4; - prop->res = 0; - prop->data[0] = 4; /* alignment */ - prop = RPI_PROP_NEXT(prop); - - prop->id = 0; - prop->size = 0; - prop->res = 0; - prop = RPI_PROP_NEXT(prop); - - printf("Requesting video mode: %dx%d (%d bpp)\n", fb_width, fb_height, fb_depth); - - rpi_mbox_send(RPI_MBOX_PROP, RPI_MEM_BUS_COHERENT(propbuf)); - while(rpi_mbox_recv(RPI_MBOX_PROP) != RPI_MEM_BUS_COHERENT(propbuf)); - - hexdump(propbuf, sizeof propbuf); - - if(hdr->res != 0x80000000) { - printf("Request failed, error: %x\n", hdr->res); - return -1; - } + printf("Requesting video mode: %dx%d %d bpp (fb:%dx%d)\n", scr_width, scr_height, + fb_depth, fb_width, fb_height); + + rpi_prop(RPI_TAG_ALLOCFB, 16); + rpi_prop(RPI_TAG_SETFBVIRT, scr_width, scr_height); + rpi_prop(RPI_TAG_SETFBPHYS, fb_width, fb_height); + rpi_prop(RPI_TAG_SETFBDEPTH, fb_depth); - prop = (struct rpi_prop*)(hdr + 1); - while(prop->id) { - prop->size = prop->res & 0x7fffffff; + rpi_prop_send(); + while((prop = rpi_prop_next())) { switch(prop->id) { case RPI_TAG_SETFBPHYS: - printf("setfbphys"); + printf(" setfbphys"); + fb_width = prop->data[0]; + fb_height = prop->data[1]; break; case RPI_TAG_SETFBVIRT: - printf("setfbvirt"); - fb_width = prop->data[0]; - fb_height = prop->data[1]; + printf(" setfbvirt"); + scr_width = prop->data[0]; + scr_height = prop->data[1]; break; case RPI_TAG_SETFBDEPTH: - printf("setfbdepth"); + printf(" setfbdepth"); fb_depth = prop->data[0]; break; case RPI_TAG_ALLOCFB: - printf("allocfb"); - fb_pixels = (void*)prop->data[0]; + printf(" allocfb"); + fb_pixels = (void*)(prop->data[0] & 0x3fffffff); fb_size = prop->data[1]; break; default: - printf("tag %x", prop->id); + printf(" tag %x", prop->id); break; } @@ -108,12 +72,100 @@ int video_init(void) printf(" %u", prop->data[i]); } putchar('\n'); - prop = RPI_PROP_NEXT(prop); } - printf("Got video mode: %dx%d (%d bpp) at %p (%d bytes)\n", fb_width, fb_height, - fb_depth, fb_pixels, fb_size); + if(!fb_pixels) { + rpi_prop(RPI_TAG_ALLOCFB, 16); + if(rpi_prop_send() == -1 || !(prop = rpi_prop_find(RPI_TAG_ALLOCFB))) { + printf("Failed to allocate framebuffer\n"); + return -1; + } + fb_pixels = (void*)(prop->data[0] & 0x3fffffff); + fb_size = prop->data[1]; + } + + rpi_prop(RPI_TAG_GETFBPITCH); + rpi_prop(RPI_TAG_GETFBOFFS); + rpi_prop_send(); + /* + if(rpi_prop_send() == -1) { + printf("Failed to get pitch\n"); + return -1; + } + */ + + while((prop = rpi_prop_next())) { + switch(prop->id) { + case RPI_TAG_GETFBPITCH: + fb_pitch = prop->data[0]; + break; + + case RPI_TAG_GETFBOFFS: + fb_xoffs = prop->data[0]; + fb_yoffs = prop->data[1]; + break; + + default: + break; + } + } + + printf("Got video mode: %dx%d (%d bpp)\n", scr_width, scr_height, fb_depth); + printf("Framebuffer: %dx%d at %p (%d bytes)\n", fb_width, fb_height, fb_pixels, fb_size); + printf(" virtual offset: %d, %d\n", fb_xoffs, fb_yoffs); + printf(" scanline pitch: %d\n", fb_pitch); + + fbptr = fb_pixels; + for(i=0; i> 1; + int b = (i ^ j) >> 2; + fbptr[j] = b | (g << 8) | (r << 16) | 0xff000000; + } + fbptr += fb_pitch >> 2; + } - memset(fb_pixels, 0, fb_size); + sysctl_dcache_clean_inval((uint32_t)fb_pixels, fb_size); return 0; } + +int video_scroll(int x, int y) +{ + struct rpi_prop *prop; + + rpi_prop(RPI_TAG_SETFBOFFS, x, y); + if(rpi_prop_send() == -1 || !(prop = rpi_prop_find(RPI_TAG_SETFBOFFS))) { + return -1; + } + + fb_xoffs = prop->data[0]; + fb_yoffs = prop->data[1]; + return 0; +} + +void video_update(int dt) +{ + static int dirx = 1, diry = 1; + int nx, ny; + + nx = fb_xoffs + dirx * dt; + ny = fb_yoffs + diry * dt; + + if(nx < 0) { + nx = 0; + dirx = -dirx; + } else if(nx + scr_width >= fb_width) { + nx = fb_width - scr_width; + dirx = -dirx; + } + if(ny < 0) { + ny = 0; + diry = -diry; + } else if(ny + scr_height >= fb_height) { + ny = fb_height - scr_height; + diry = -diry; + } + + video_scroll(nx, ny); +} diff --git a/src/video.h b/src/video.h index 5c524b4..10c9f6c 100644 --- a/src/video.h +++ b/src/video.h @@ -3,4 +3,8 @@ int video_init(void); +int video_scroll(int x, int y); + +void video_update(int dt); + #endif /* VIDEO_H_ */