#define _GNU_SOURCE
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <string.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/param.h>
#include "devopen.h"
#include "dev/pci/sis1100_var.h"

static void
close_dev(struct devinfo* info)
{
    if (info->base_ctrl) {
        munmap((void*)(info->base_ctrl), info->size_ctrl);
        info->base_ctrl=MAP_FAILED;
        info->size_ctrl=0;
    }
    if (info->base_remote) {
        munmap((void*)(info->base_remote), info->size_remote);
        info->base_remote=MAP_FAILED;
        info->size_remote=0;
    }
    close(info->p_ctrl); info->p_ctrl=-1;
    close(info->p_remote); info->p_remote=-1;
    close(info->p_ram); info->p_ram=-1;
    close(info->p_dsp); info->p_dsp=-1;
}

static volatile u_int32_t*
mmap_region(int p, u_int32_t* size)
{
    volatile u_int32_t* base;

    if (ioctl(p, SIS1100_MAPSIZE, size)<0) {
        fprintf(stderr, "ioctl(%d, SIS1100_MAPSIZE): %s\n",
            p, strerror(errno));
        return MAP_FAILED;
    }
    base=mmap(0, *size, PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED,
        p, 0);
    if (base==MAP_FAILED) {
        fprintf(stderr, "mmap(%d, size=%d): %s\n", p, *size, strerror(errno));
    }
    return base;
}

int
open_dev(char** pathes, struct devinfo* info)
{
    const char* path;

    info->p_ctrl=-1;
    info->p_remote=-1;
    info->p_ram=-1;
    info->p_dsp=-1;
    info->base_ctrl=MAP_FAILED;
    info->base_remote=MAP_FAILED;
    info->size_ctrl=0;
    info->size_remote=0;
    info->close=0;

    path=*pathes++;
    while (path) {
        int p;
        enum sis1100_subdev subdev;

        p=open(path, O_RDWR, 0);
        if (p<0) {
            fprintf(stderr, "open \"%s\": %s\n", path, strerror(errno));
            goto error;
        }
        if (ioctl(p, SIS1100_DEVTYPE, &subdev)<0) {
            fprintf(stderr, "ioctl(\"%s\", SIS1100_DEVTYPE): %s\n",
                    path, strerror(errno));
            goto error;
        }
        switch (subdev) {
        case sis1100_subdev_remote:
            info->p_remote=p;
            info->base_remote=mmap_region(p, &info->size_remote);
            printf("base_remote=%p\n", info->base_remote);
            break;
        case sis1100_subdev_ram:
            info->p_ram=p;
            break;
        case sis1100_subdev_ctrl:
            info->p_ctrl=p;
            info->base_ctrl=mmap_region(p, &info->size_ctrl);
            printf("base_ctrl=%p\n", info->base_ctrl);
            break;
        case sis1100_subdev_dsp:
            info->p_dsp=p;
            break;
        }
        path=*pathes++;
    }

    info->close=close_dev;
    return 0;

error:
    close_dev(info);
    return -1;
}
