#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <inttypes.h>
#include <poll.h>

#define MAILBOX_BASE 0x20041000
#define MAILBOX_MAP_SIZE 0x1000
#define MAILBOX_TX_OFFSET 0x000
#define MAILBOX_RX_OFFSET 0x300
#define MAILBOX_REG_OFFSET_CMD 0x0
#define MAILBOX_REG_OFFSET_PTR 0x4
#define MAILBOX_REG_OFFSET_STS 0x8
#define MAILBOX_REG_OFFSET_MSK 0xC
#define MAILBOX_REG_VALUE_STS_PENDING 0x1
#define MAILBOX_REG_VALUE_STS_SPACE 0x2
#define MAILBOX_REG_VALUE_MSK_PENDING 0x1
#define MAILBOX_REG_VALUE_MSK_SPACE 0x2


static int uio_irq_enable(int uiofd)
{
    uint32_t one = 1;
    ssize_t w = write(uiofd, &one, sizeof(one));
    return (w == (ssize_t)sizeof(one)) ? 0 : -1;
}


static int uio_wait_irq(int uiofd)
{
    uint32_t irq_count;
    ssize_t size;

    size = read(uiofd, &irq_count, sizeof(irq_count));

    return (size == (ssize_t)sizeof(irq_count)) ? 0 : -1;
}


int main(void)
{
    const char *uio = "/dev/uio0";

    /* Open /dev/uio0 and map memory */
    int fd = open(uio, O_RDWR | O_SYNC);
    if (fd < 0) { perror("ERROR: open /dev/uio0"); return 1; }

    /* Map Mailbox */
    void *mailbox_map = mmap(NULL, MAILBOX_MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (mailbox_map == MAP_FAILED) { perror("ERROR: mmap mailbox"); return 1; }

    /* Set register address */
    volatile uint32_t *mailbox_reg = (volatile uint32_t *)mailbox_map;
    volatile uint32_t *mailbox_reg_tx = mailbox_reg + (MAILBOX_TX_OFFSET / 4);
    volatile uint32_t *mailbox_reg_rx = mailbox_reg + (MAILBOX_RX_OFFSET / 4);

    /* Initialize Mailbox */
    uint32_t reg_value;
    // Disable TX interrupt
    reg_value = *(mailbox_reg_tx + (MAILBOX_REG_OFFSET_MSK / 4));
    *(mailbox_reg_tx + (MAILBOX_REG_OFFSET_MSK / 4)) = reg_value & ~MAILBOX_REG_VALUE_MSK_SPACE;
    // Enable RX interrupt
    reg_value = *(mailbox_reg_rx + (MAILBOX_REG_OFFSET_MSK / 4));
    *(mailbox_reg_rx + (MAILBOX_REG_OFFSET_MSK / 4)) = reg_value | MAILBOX_REG_VALUE_MSK_PENDING;

    /* Change LED blink period */
    while (1) {

        int period;

        /* UIO IRQ enable */
        if (uio_irq_enable(fd) < 0) {
            perror("ERROR: uio_irq_enable");
            break;
        }

        /* Input LED Blink period */
        printf("$ Input LED Blink period (100 - 10000 [msec])\n");
        printf("-> ");
        scanf("%d", &period);

        /* Send command */
        printf("\nSend command: LED blink period=%d [msec]\n", period);
        *mailbox_reg_tx = period;

        /* Receive command */
        printf("Waiting for mailbox IRQ\n");
        if (uio_wait_irq(fd) < 0) {
            perror("ERROR: uio_wait_irq");
            break;
        }

        /* Check Mailbox status */
        reg_value = *(mailbox_reg_rx + (MAILBOX_REG_OFFSET_STS / 4));
        if ((reg_value & MAILBOX_REG_VALUE_STS_PENDING) != MAILBOX_REG_VALUE_STS_PENDING) {
            printf("ERROR: Mailbox status is not Pending, reg_value=0x%08X\n", reg_value);
            break;
        }

        /* Get command */
        reg_value = *(mailbox_reg_rx + (MAILBOX_REG_OFFSET_CMD / 4));
        printf("Get command: LED blink period change count=%d\n\n", reg_value);
    }

    /* Cleanup */
    munmap(mailbox_map, MAILBOX_MAP_SIZE);
    close(fd);

    return 0;
}
