/**
* DISCLAIMER
* This is a solution written from memory after the test, before even knowing my grade (altough I passed every public test in the exam).
* There may be a mistake in my code or reasoning that I did not catch at the time. Moreover, I may be misremembering or mistyping something
* so don't be surprised if this doesn't even compile. If you spot any mistake let me know (you may add comments to this file on google drive).
*
* In short, NOTHING IN THIS FILE IS GUARANTEED TO BE CORRECT.
*
* This is an mix between what I recall from my solution, some extra checks (like calling free to avoid memory leaks) that probably won't even
* be evaluated by the LCF and changes that I thought would make my solution easier to understand. There are also comments in some parts explaining
* why I did what I did or some LCF issues I stumbled upon.
*/
static uint16_t x_res, y_res;
static uint8_t bpp, bypp, r_pos, r_sz, b_pos, b_sz, g_pos, g_sz;
static size_t video_mem_size;
static phys_addr_t phys_addr;
static uint8_t *video_mem;
static uint8_t *first_square_back_buffer;
static uint8_t *second_square_back_buffer;
#define BIT(n) (1<<(n))
#define VERT_RETR_INT BIT(7)
#define CONTROL_REG 0x28
#define IRQ_LINE 7
#define FPS 30
int util_sys_inb(int port, uint8_t *byte) {
uint32_t val;
if (sys_inb(port, &val))
return 1;
*byte = val;
return 0;
}
int draw_pixel(uint8_t *buf, uint16_t x, uint16_t y, uint32_t color) {
if (x >= x_res || y >= y_res)
return 0; // it's not an error to be out of bounds, but nothing should be drawn
uint8_t *mem_pos = buf + (y*x_res + x)*bypp;
color &= 0x00ffffff; // I needed to ignore the most significant bits to pass the tests in mode 2
if (memcpy(mem_pos, &color, bypp) == NULL)
return 1;
return 0;
}
int draw_hline(uint8_t *buf, uint16_t x, uint16_t y, uint16_t len, uint32_t color) {
for (int i = 0; i < len; i++) {
if (draw_pixel(buf, x+i, y, color))
return 1;
}
return 0;
}
int draw_square(uint8_t *buf, uint16_t x, uint16_t y, uint16_t len, uint32_t color) {
for (int i = 0; i < len; i++) {
if (draw_hline(buf, x, y+i, color))
return 1;
}
return 0;
}
int part1(uint16_t x, uint16_t y, uint16_t len, uint32_t color, uint8_t delay) {
if (memcpy(video_mem, first_square_back_buffer, video_mem_size) == NULL)
return 1;
sleep(delay);
return 0;
}
static int hook_id = 1;
int part2(uint16_t x, uint16_t y, uint16_t len, uint32_t color, uint8_t delay) {
int ipc_status, irq_set = BIT(hook_id);
message msg;
bool fail = false;
if (sys_irqsetpolicy(IRQ_LINE, IRQ_REENABLE, &hook_id))
return 1;
uint8_t ticks = delay * FPS * 2;
while( ticks > 0 ) {
/* Get a request message. */
int r;
if ( (r = driver_receive(ANY, &msg, &ipc_status)) != 0) {
printf("driver_receive failed with: %d", r);
continue;
}
if (is_ipc_notify(ipc_status)) { /* received notification */
switch (_ENDPOINT_P(msg.m_source)) {
case HARDWARE: /* hardware interrupt notification */
if (msg.m_notify.interrupts & irq_set) {
if (ticks > delay * FPS) {
if (memcpy(video_mem, first_square_back_buffer, video_mem_size) == NULL) {
fail = true;
break;
}
} else {
if (memcpy(video_mem, second_square_back_buffer, video_mem_size) == NULL) {
fail = true;
break;
}
}
ticks--;
}
break;
default:
break;
}
} else {
/* no standard messages expected: do nothing */
}
}
if (sys_irqrmpolicy(&hook_id))
return 1;
return fail;
}
int test_draw_square(uint32_t mode, uint16_t x, uint16_t y, uint16_t len, uint32_t color, uint8_t delay) {
mode_info_t inf;
if (get_mode_info(mode, &inf))
return 1;
x_res = inf.x_res;
y_res = inf.y_res;
bpp = inf.bpp;
bypp = (bpp%8 == 0)? (bpp/8) : (bpp/8 + 1);
r_pos = inf.r_pos;
r_sz = inf.r_sz;
g_pos = inf.g_pos;
g_sz = inf.g_sz;
b_pos = inf.b_pos;
b_sz = inf.b_sz;
phys_addr = inf.phys_addr;
video_mem_size = x_res * y_res * bypp;
video_mem = map_phys_mem(phys_addr, video_mem_size);
// The next two lines are not mandatory, but are used for this solution. An explanation can be found below
first_square_back_buffer = malloc(video_mem_size);
second_square_back_buffer = malloc(video_mem_size);
if (video_mem == NULL || first_square_back_buffer == NULL || second_square_back_buffer == NULL) {
free(first_square_back_buffer);
free(second_square_back_buffer);
return 1;
}
uint8_t cmd;
if (util_sys_inb(CONTROL_REG, &cmd)) { // Just in case, so that the unknown bytes are not modified
free(first_square_back_buffer);
free(second_square_back_buffer);
return 1;
}
cmd &= 0xf0;
cmd |= mode;
/*
* For modularity I initially didn't have the following line. I would only set VERT_RETR_INT in part2,
* calling util_sys_inb again and updating the control word. This would result in the test not passing.
*
* Although that solution would be correct, the LCF is not smart enough to detect it as valid, because it
* was not prepared to handle multiple writes to the control word.
*/
cmd |= VERT_RETR_INT; // Comment this line if you wish to go with part 1
if (sys_outb(CONTROL_REG, cmd)) {
free(first_square_back_buffer);
free(second_square_back_buffer);
return 1;
}
/*
* There are many ways to implement this. For the part 1 you could just draw directly into the video_mem.
*
* For the second part, as you need to erase the first square before drawing the second one you could draw a black
* square before drawing the second square. This would cause flickering on my machine (although it passes the public tests, so I'm not sure
* if LCF is even able to spot that problem) which could be fixed if the square was only erased in the transition frame instead of every frame.
*
* However I think a solution with back buffers is simpler and less error prone (maybe you don't share the same opinion though). Basically, as there are
* only two static images (squares), I drew each in a separate auxiliary frame buffer and used memcpy to show the frame I wanted.
*/
if (draw_square(first_square_back_buffer, x, y, len, color)) {
free(first_square_back_buffer);
free(second_square_back_buffer);
return 1;
}
if (draw_square(second_square_back_buffer, y, x, len, color)) { // You can ignore this one for part 1.
free(first_square_back_buffer);
free(second_square_back_buffer);
return 1;
}
int vr;
// Part 1
//vr = part1(x, y, len, color, delay);
// Part 2.
vr = part2(x, y, len, color, delay);
free(first_square_back_buffer);
free(second_square_back_buffer);
cmd &= BIT(4) | BIT(5) | BIT(6);
if (sys_outb(CONTROL_REG, cmd))
return 1;
return vr;
}