#define _GNU_SOURCE

#include "libxlu_internal.h"
#include "libxlu_disk_l.h"
#include "libxlu_disk_i.h"
#include "libxlu_cfg_i.h"


#define XLU__PCI_ERR(_c, _x, _a...) \
    if((_c) && (_c)->report) fprintf((_c)->report, _x, ##_a)

static int hex_convert(const char *str, unsigned int *val, unsigned int mask)
{
    unsigned long ret;
    char *end;

    ret = strtoul(str, &end, 16);
    if ( end == str || *end != '\0' )
        return -1;
    if ( ret & ~mask )
        return -1;
    *val = (unsigned int)ret & mask;
    return 0;
}

static int pcidev_struct_fill(libxl_device_pci *pcidev, unsigned int domain,
                               unsigned int bus, unsigned int dev,
                               unsigned int func, unsigned int vdevfn)
{
    pcidev->domain = domain;
    pcidev->bus = bus;
    pcidev->dev = dev;
    pcidev->func = func;
    pcidev->vdevfn = vdevfn;
    return 0;
}

#define STATE_DOMAIN    0
#define STATE_BUS       1
#define STATE_DEV       2
#define STATE_FUNC      3
#define STATE_VSLOT     4
#define STATE_OPTIONS_K 6
#define STATE_OPTIONS_V 7
#define STATE_TERMINAL  8
#define STATE_TYPE      9
#define STATE_RDM_STRATEGY      10
#define STATE_RESERVE_POLICY    11
#define INVALID         0xffffffff
int xlu_pci_parse_bdf(XLU_Config *cfg, libxl_device_pci *pcidev, const char *str)
{
    unsigned state = STATE_DOMAIN;
    unsigned dom = INVALID, bus = INVALID, dev = INVALID, func = INVALID, vslot = 0;
    char *buf2, *tok, *ptr, *end, *optkey = NULL;

    if ( NULL == (buf2 = ptr = strdup(str)) )
        return ERROR_NOMEM;

    for(tok = ptr, end = ptr + strlen(ptr) + 1; ptr < end; ptr++) {
        switch(state) {
        case STATE_DOMAIN:
            if ( *ptr == ':' ) {
                state = STATE_BUS;
                *ptr = '\0';
                if ( hex_convert(tok, &dom, 0xffff) )
                    goto parse_error;
                tok = ptr + 1;
            }
            break;
        case STATE_BUS:
            if ( *ptr == ':' ) {
                state = STATE_DEV;
                *ptr = '\0';
                if ( hex_convert(tok, &bus, 0xff) )
                    goto parse_error;
                tok = ptr + 1;
            }else if ( *ptr == '.' ) {
                state = STATE_FUNC;
                *ptr = '\0';
                if ( dom & ~0xff )
                    goto parse_error;
                bus = dom;
                dom = 0;
                if ( hex_convert(tok, &dev, 0xff) )
                    goto parse_error;
                tok = ptr + 1;
            }
            break;
        case STATE_DEV:
            if ( *ptr == '.' ) {
                state = STATE_FUNC;
                *ptr = '\0';
                if ( hex_convert(tok, &dev, 0xff) )
                    goto parse_error;
                tok = ptr + 1;
            }
            break;
        case STATE_FUNC:
            if ( *ptr == '\0' || *ptr == '@' || *ptr == ',' ) {
                switch( *ptr ) {
                case '\0':
                    state = STATE_TERMINAL;
                    break;
                case '@':
                    state = STATE_VSLOT;
                    break;
                case ',':
                    state = STATE_OPTIONS_K;
                    break;
                }
                *ptr = '\0';
                if ( !strcmp(tok, "*") ) {
                    pcidev->vfunc_mask = LIBXL_PCI_FUNC_ALL;
                }else{
                    if ( hex_convert(tok, &func, 0x7) )
                        goto parse_error;
                    pcidev->vfunc_mask = (1 << 0);
                }
                tok = ptr + 1;
            }
            break;
        case STATE_VSLOT:
            if ( *ptr == '\0' || *ptr == ',' ) {
                state = ( *ptr == ',' ) ? STATE_OPTIONS_K : STATE_TERMINAL;
                *ptr = '\0';
                if ( hex_convert(tok, &vslot, 0xff) )
                    goto parse_error;
                tok = ptr + 1;
            }
            break;
        case STATE_OPTIONS_K:
            if ( *ptr == '=' ) {
                state = STATE_OPTIONS_V;
                *ptr = '\0';
                optkey = tok;
                tok = ptr + 1;
            }
            break;
        case STATE_OPTIONS_V:
            if ( *ptr == ',' || *ptr == '\0' ) {
                state = (*ptr == ',') ? STATE_OPTIONS_K : STATE_TERMINAL;
                *ptr = '\0';
                if ( !strcmp(optkey, "msitranslate") ) {
                    pcidev->msitranslate = atoi(tok);
                }else if ( !strcmp(optkey, "power_mgmt") ) {
                    pcidev->power_mgmt = atoi(tok);
                }else if ( !strcmp(optkey, "permissive") ) {
                    pcidev->permissive = atoi(tok);
                }else if ( !strcmp(optkey, "seize") ) {
                    pcidev->seize = atoi(tok);
                } else if (!strcmp(optkey, "rdm_policy")) {
                    if (!strcmp(tok, "strict")) {
                        pcidev->rdm_policy = LIBXL_RDM_RESERVE_POLICY_STRICT;
                    } else if (!strcmp(tok, "relaxed")) {
                        pcidev->rdm_policy = LIBXL_RDM_RESERVE_POLICY_RELAXED;
                    } else {
                        XLU__PCI_ERR(cfg, "%s is not an valid PCI RDM property"
                                          " policy: 'strict' or 'relaxed'.",
                                     tok);
                        goto parse_error;
                    }
                } else {
                    XLU__PCI_ERR(cfg, "Unknown PCI BDF option: %s", optkey);
                }
                tok = ptr + 1;
            }
        default:
            break;
        }
    }

    if ( tok != ptr || state != STATE_TERMINAL )
        goto parse_error;

    assert(dom != INVALID && bus != INVALID && dev != INVALID && func != INVALID);

    /* Just a pretty way to fill in the values */
    pcidev_struct_fill(pcidev, dom, bus, dev, func, vslot << 3);

    free(buf2);

    return 0;

parse_error:
    free(buf2);
    return ERROR_INVAL;
}

int xlu_rdm_parse(XLU_Config *cfg, libxl_rdm_reserve *rdm, const char *str)
{
    unsigned state = STATE_TYPE;
    char *buf2, *tok, *ptr, *end;

    if (NULL == (buf2 = ptr = strdup(str)))
        return ERROR_NOMEM;

    for (tok = ptr, end = ptr + strlen(ptr) + 1; ptr < end; ptr++) {
        switch(state) {
        case STATE_TYPE:
            if (*ptr == '=') {
                *ptr = '\0';
                if (!strcmp(tok, "strategy")) {
                    state = STATE_RDM_STRATEGY;
                } else if (!strcmp(tok, "policy")) {
                    state = STATE_RESERVE_POLICY;
                } else {
                    XLU__PCI_ERR(cfg, "Unknown RDM state option: %s", tok);
                    goto parse_error;
                }
                tok = ptr + 1;
            }
            break;
        case STATE_RDM_STRATEGY:
            if (*ptr == '\0' || *ptr == ',') {
                state = *ptr == ',' ? STATE_TYPE : STATE_TERMINAL;
                *ptr = '\0';
                if (!strcmp(tok, "host")) {
                    rdm->strategy = LIBXL_RDM_RESERVE_STRATEGY_HOST;
                } else {
                    XLU__PCI_ERR(cfg, "Unknown RDM strategy option: %s", tok);
                    goto parse_error;
                }
                tok = ptr + 1;
            }
            break;
        case STATE_RESERVE_POLICY:
            if (*ptr == ',' || *ptr == '\0') {
                state = *ptr == ',' ? STATE_TYPE : STATE_TERMINAL;
                *ptr = '\0';
                if (!strcmp(tok, "strict")) {
                    rdm->policy = LIBXL_RDM_RESERVE_POLICY_STRICT;
                } else if (!strcmp(tok, "relaxed")) {
                    rdm->policy = LIBXL_RDM_RESERVE_POLICY_RELAXED;
                } else {
                    XLU__PCI_ERR(cfg, "Unknown RDM property policy value: %s",
                                 tok);
                    goto parse_error;
                }
                tok = ptr + 1;
            }
        default:
            break;
        }
    }

    if (tok != ptr || state != STATE_TERMINAL)
        goto parse_error;

    free(buf2);

    return 0;

parse_error:
    free(buf2);
    return ERROR_INVAL;
}

/*
 * Local variables:
 * mode: C
 * c-basic-offset: 4
 * indent-tabs-mode: nil
 * End:
 */
