/* _NVRM_COPYRIGHT_BEGIN_
 *
 * Copyright 2001-2002 by NVIDIA Corporation.  All rights reserved.  All
 * information contained herein is proprietary and confidential to NVIDIA
 * Corporation.  Any use, reproduction, or disclosure without the written
 * permission of NVIDIA Corporation is prohibited.
 *
 * _NVRM_COPYRIGHT_END_
 */

#include "nv-misc.h"
#include "os-interface.h"
#include "nv.h"
#include "nv-freebsd.h"

struct sysctl_ctx_list sysctl_ctx;

struct sysctl_oid *oid_nvidia;
struct sysctl_oid *oid_agp;
struct sysctl_oid *oid_registry;

static char *option_string = NULL;

static void*  nvidia_find_bridge     (void);
static U032   nvidia_dev_agp_cmd     (device_t dev);
static U032   nvidia_dev_agp_status  (device_t dev);
static U032   nvidia_sys_agp_cmd     (device_t dev);

void nvidia_sysctl_init(void)
{
    device_t dev;
    struct sysctl_oid *oid;
    nv_parm_t *entry;

    sysctl_ctx_init(&sysctl_ctx);

    oid_nvidia = SYSCTL_ADD_NODE(&sysctl_ctx,
            SYSCTL_STATIC_CHILDREN(_hw),
            OID_AUTO,
            "nvidia",
            CTLFLAG_RD | CTLFLAG_DYN,
            0,
            "NVIDIA SYSCTL Master Node");

    oid_agp = SYSCTL_ADD_NODE(&sysctl_ctx,
            SYSCTL_CHILDREN(oid_nvidia),
            OID_AUTO,
            "agp",
            CTLFLAG_RD | CTLFLAG_DYN,
            0,
            "NVIDIA SYSCTL AGP Node");

    SYSCTL_ADD_STRING(&sysctl_ctx,
            SYSCTL_CHILDREN(oid_nvidia),
            OID_AUTO,
            "version",
            CTLFLAG_RD | CTLFLAG_DYN,
            (char *)(uintptr_t) pNVRM_ID,
            0,
            "NVIDIA Resource Manager (NVRM) Version");

    oid_registry = SYSCTL_ADD_NODE(&sysctl_ctx,
            SYSCTL_CHILDREN(oid_nvidia),
            OID_AUTO,
            "registry",
            CTLFLAG_RD | CTLFLAG_DYN,
            0,
            "NVIDIA SYSCTL Registry Node");

    entry = nv_parms;
    do {
        SYSCTL_ADD_PROC(&sysctl_ctx,
            SYSCTL_CHILDREN(oid_registry),
            OID_AUTO,
            entry->name,
            CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_DYN,
            entry->data, 0,
            nvidia_sysctl_registry_key,
            "IU", NULL);
        entry++;
    } while(entry->name != NULL);

    option_string = malloc(1, M_NVIDIA, M_WAITOK | M_ZERO);

    SYSCTL_ADD_PROC(&sysctl_ctx,
            SYSCTL_CHILDREN(oid_registry),
            OID_AUTO, "dwords",
            CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_DYN,
            NULL, 0,
            nvidia_sysctl_registry_dwords,
            "A", NULL);

    if ((dev = nvidia_find_bridge()) != NULL) {
        /*
         * Assume this is the only AGP capable bridge in the system
         * and report its capabilities (hw.nvidia.agp.bridge).
         */
        oid = SYSCTL_ADD_NODE(&sysctl_ctx,
                SYSCTL_CHILDREN(oid_agp),
                OID_AUTO,
                "host-bridge",
                CTLFLAG_RD | CTLFLAG_DYN,
                0,
                "NVIDIA AGP Bridge Node");

        SYSCTL_ADD_PROC(&sysctl_ctx,
                SYSCTL_CHILDREN(oid),
                OID_AUTO,
                "rates",
                CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_DYN,
                (void *) dev, 0,
                nvidia_sysctl_agp_rates,
                "A",
                "NVIDIA AGP Bridge Rates Info");

        SYSCTL_ADD_PROC(&sysctl_ctx,
                SYSCTL_CHILDREN(oid),
                OID_AUTO,
                "fw",
                CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_DYN,
                (void *) dev, 0,
                nvidia_sysctl_agp_fw,
                "A",
                "NVIDIA AGP Bridge FW Info");

        SYSCTL_ADD_PROC(&sysctl_ctx,
                SYSCTL_CHILDREN(oid),
                OID_AUTO,
                "sba",
                CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_DYN,
                (void *) dev, 0,
                nvidia_sysctl_agp_sba,
                "A",
                "NVIDIA AGP Bridge SBA Info");

        SYSCTL_ADD_PROC(&sysctl_ctx,
                SYSCTL_CHILDREN(oid),
                OID_AUTO,
                "registers",
                CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_DYN,
                (void *) dev, 0,
                nvidia_sysctl_agp_registers,
                "A",
                "NVIDIA AGP Bridge Registers");
    }
}

void nvidia_sysctl_exit(void)
{
    sysctl_ctx_free(&sysctl_ctx);
    if (option_string != NULL)
        free((void *)option_string, M_NVIDIA);
}


static U032 nvidia_dev_agp_cmd(device_t dev)
{
    U008 cap_ptr;
    U032 tmp;

    cap_ptr = nvidia_pci_find_capability(dev, PCIR_CAP_ID_AGP);
    tmp = pci_read_config(dev, cap_ptr + 8, 4);

    return tmp;
}

static U032 nvidia_dev_agp_status(device_t dev)
{
    U008 cap_ptr;
    U032 tmp;

    cap_ptr = nvidia_pci_find_capability(dev, PCIR_CAP_ID_AGP);
    tmp = pci_read_config(dev, cap_ptr + 4, 4);

    return tmp;
}

static U032 nvidia_sys_agp_cmd(device_t dev)
{
    U032 tmp;

    tmp = nvidia_dev_agp_cmd(dev);
    tmp &= nvidia_dev_agp_cmd(nvidia_find_bridge());

    return tmp;
}

static void* nvidia_find_bridge(void)
{
    U008 hdrtype;
    U032 bus;
    U032 device;
    U032 func;

    device_t dev;

    for (bus = 0; bus < 5; bus++) {
        for (device = 0; device < 32; device++) {
            for (func = 0; func < 8; func++) {
                /*
                 * We're not interested in non-bridge PCI devices, nor do
                 * care for devices with our own or an invalid vendor ID.
                 */
                dev = pci_find_bsf(bus, device, func);

                if (dev == NULL)
                {
                    if (func == 0)
                    {
                        // If a read to function zero of a specified bus/device master aborts, 
                        // then it is assumed that no such device exists on the bus since 
                        // devices are required to implement function number zero. 
                        // In this case reads to the remaining functions are not necessary.
                        break;
                    }
                    else
                    {
                        continue;
                    }
                }

                if (pci_get_class(dev) != PCIC_BRIDGE)
                    break;

                if (pci_get_vendor(dev) == 0xffff)
                    break;

                if (nvidia_pci_find_capability(dev, PCIR_CAP_ID_AGP) != 0)
                    return dev;

                /*
                 * It also doesn't make sense to to iterate over multiple
                 * functions if this isn't a multi-function device.
                 */
                hdrtype = pci_read_config(dev, PCIR_HDRTYPE, 1);

                if ((hdrtype & PCIM_MFDEV) == 0)
                    break;
            }
        }
    }

    return NULL;
}


int nvidia_sysctl_dev_model(SYSCTL_HANDLER_ARGS)
{
    nv_state_t *nv = arg1;
    nv_stack_t *sp;

    char model_name[NV_DEVICE_NAME_LENGTH+1];
    U016 id = nv->device_id;

    NV_UMA_ZONE_ALLOC_STACK(sp);
    if (sp == NULL)
        return ENOMEM;

    if (rm_get_device_name(sp, nv, id, NV_DEVICE_NAME_LENGTH, model_name)
            != RM_OK) {
        strcpy(model_name, "Unknown");
    }

    NV_UMA_ZONE_FREE_STACK(sp);

    return SYSCTL_OUT(req, model_name, strlen(model_name) + 1);
}

int nvidia_sysctl_dev_vbios(SYSCTL_HANDLER_ARGS)
{
    nv_state_t *nv = arg1;
    nv_stack_t *sp;

    U032 vbios[5];
    U008 vbios_version[16];

    NV_UMA_ZONE_ALLOC_STACK(sp);
    if (sp == NULL)
        return ENOMEM;

    if (rm_get_vbios_version(sp, nv, &vbios[0], &vbios[1], &vbios[2],
                &vbios[3], &vbios[4]) != RM_OK) {
        /*
         * The VBIOS version is only accessible after the device has been
         * initialized with rm_init_adapter. 
         */
        sprintf(vbios_version, "??.??.??.??.??");
    } else {
        sprintf(vbios_version, "%02x.%02x.%02x.%02x.%02x", vbios[0],
                vbios[1], vbios[2], vbios[3], vbios[4]);
    }

    NV_UMA_ZONE_FREE_STACK(sp);

    return SYSCTL_OUT(req, vbios_version, strlen(vbios_version) + 1);
}

int nvidia_sysctl_bus_type(SYSCTL_HANDLER_ARGS)
{
    struct nvidia_softc *sc = arg1;
    U008 bus_type[4];

    if (nvidia_pci_find_capability(sc->dev, PCIR_CAP_ID_AGP) != 0)
        sprintf(bus_type, "AGP");
    else
    if (nvidia_pci_find_capability(sc->dev, PCIR_CAP_ID_EXP) != 0)
        sprintf(bus_type, "PCI-E");
    else
        sprintf(bus_type, "PCI");

    return SYSCTL_OUT(req, bus_type, strlen(bus_type) + 1);
}

int nvidia_sysctl_registry_key(SYSCTL_HANDLER_ARGS)
{
    int error;

    error = sysctl_handle_int(oidp, arg1, 0, req);

    if (error || !req->newptr)
        return error;

    /* refresh the registry with the updated option table */
    os_registry_init();

    return 0;
}

int nvidia_sysctl_registry_dwords(SYSCTL_HANDLER_ARGS)
{
    int error, len;
    char *new_option_string;

    len = strlen(option_string) + 1;
    error = SYSCTL_OUT(req, option_string, len);

    if (error || !req->newptr)
        return error;

    len = (req->newlen - req->newidx);

    new_option_string = malloc((len + 1), M_NVIDIA, M_WAITOK);
    if (!new_option_string)
        return ENOMEM;

    error = SYSCTL_IN(req, new_option_string, len);
    if (error)
        return error;

    free(option_string, M_NVIDIA);

    option_string = new_option_string;
    option_string[len] = '\0';

    nvidia_update_registry(new_option_string);

    return 0;
}

int nvidia_sysctl_agp_rates(SYSCTL_HANDLER_ARGS)
{
    device_t dev = arg1;
    U008 agp_rates[16];

    U032 tmp = nvidia_dev_agp_status(dev);

    if ((tmp & 0x08) != 0)
        tmp = (tmp & 0x07) << 2;

    sprintf(agp_rates, "%s%s%s%s",
            (tmp & 0x0008) ? "8x " : "",
            (tmp & 0x0004) ? "4x " : "",
            (tmp & 0x0002) ? "2x " : "",
            (tmp & 0x0001) ? "1x " : "");

    return SYSCTL_OUT(req, agp_rates, strlen(agp_rates) + 1);
}

int nvidia_sysctl_agp_fw(SYSCTL_HANDLER_ARGS)
{
    device_t dev = arg1;
    U008 agp_fw[16];

    sprintf(agp_fw, "%ssupported",
        ((nvidia_dev_agp_status(dev) & 0x10) == 0x10) ? "" : "not ");

    return SYSCTL_OUT(req, agp_fw, strlen(agp_fw) + 1);
}

int nvidia_sysctl_agp_sba(SYSCTL_HANDLER_ARGS)
{
    device_t dev = arg1;
    U008 agp_sba[16];

    sprintf(agp_sba, "%ssupported",
        ((nvidia_dev_agp_status(dev) & 0x200) == 0x200) ? "" : "not ");

    return SYSCTL_OUT(req, agp_sba, strlen(agp_sba) + 1);
}

int nvidia_sysctl_agp_registers(SYSCTL_HANDLER_ARGS)
{
    device_t dev = arg1;
    U008 agp_registers[24];

    sprintf(agp_registers, "0x%08x:0x%08x",
        nvidia_dev_agp_status(dev), nvidia_dev_agp_cmd(dev));

    return SYSCTL_OUT(req, agp_registers, strlen(agp_registers) + 1);
}

int nvidia_sysctl_agp_driver(SYSCTL_HANDLER_ARGS)
{
    nv_state_t *nv = arg1;
    U008 agp_driver[24];

    switch (nv->agp_config) {
        case NVOS_AGP_CONFIG_DISABLE_AGP:
            sprintf(agp_driver, "n/a (unused)");
            break;
        case NVOS_AGP_CONFIG_OSAGP:
            sprintf(agp_driver, "freebsd (agp.ko)");
            break;
        case NVOS_AGP_CONFIG_NVAGP:
            sprintf(agp_driver, "nvidia");
            break;
    }

    return SYSCTL_OUT(req, agp_driver, strlen(agp_driver) + 1);
}

int nvidia_sysctl_agp_rate_status(SYSCTL_HANDLER_ARGS)
{
    device_t dev = arg1;
    U008 agp_rate[16];
    
    U032 tmp = nvidia_sys_agp_cmd(dev);

    if ((tmp & 0x100) && (tmp &= 0x07)) {
        sprintf(agp_rate, "%1dx",
            (nvidia_dev_agp_status(dev) & 0x08) ? (tmp << 2) : tmp);
    } else {
        sprintf(agp_rate, "n/a (disabled)");
    }

    return SYSCTL_OUT(req, agp_rate, strlen(agp_rate) + 1);
}

int nvidia_sysctl_agp_fw_status(SYSCTL_HANDLER_ARGS)
{
    device_t dev = arg1;
    U008 agp_fw[16];

    U032 tmp = nvidia_sys_agp_cmd(dev);

    if ((tmp & 0x100) == 0x100) {
        sprintf(agp_fw, "%s",
            ((tmp & 0x10) == 0x10) ? "enabled" : "disabled");
    } else {
        sprintf(agp_fw, "n/a (disabled)");
    }

    return SYSCTL_OUT(req, agp_fw, strlen(agp_fw) + 1);
}

int nvidia_sysctl_agp_sba_status(SYSCTL_HANDLER_ARGS)
{
    device_t dev = arg1;
    U008 agp_sba[16];

    U032 tmp = nvidia_sys_agp_cmd(dev);

    if ((tmp & 0x100) == 0x100) {
        sprintf(agp_sba, "%s",
            ((tmp & 0x200) == 0x200) ? "enabled" : "disabled");
    } else {
        sprintf(agp_sba, "n/a (disabled)");
    }

    return SYSCTL_OUT(req, agp_sba, strlen(agp_sba) + 1);
}

int nvidia_sysctl_agp_status(SYSCTL_HANDLER_ARGS)
{
    device_t dev = arg1;
    U008 agp_cmd[16];

    sprintf(agp_cmd, "%s",
        (nvidia_sys_agp_cmd(dev) & 0x100) ? "enabled" : "disabled");

    return SYSCTL_OUT(req, agp_cmd, strlen(agp_cmd) + 1);
}

void nv_sysctl_init(nv_state_t *nv)
{
    struct sysctl_oid *oid;
    struct nvidia_softc *sc = nv->os_state;

    char name[4];
    sprintf(name, "%d", device_get_unit(sc->dev));

    sysctl_ctx_init(&sc->sysctl_ctx);

    oid = SYSCTL_ADD_NODE(&sc->sysctl_ctx,
            SYSCTL_CHILDREN(oid_nvidia),
            OID_AUTO,
            "cards",
            CTLFLAG_RD | CTLFLAG_DYN,
            0,
            "NVIDIA SYSCTL Cards Node");

    oid = SYSCTL_ADD_NODE(&sc->sysctl_ctx,
            SYSCTL_CHILDREN(oid),
            OID_AUTO,
            name,
            CTLFLAG_RD | CTLFLAG_DYN,
            0,
            "NVIDIA SYSCTL Device Node");

    SYSCTL_ADD_PROC(&sc->sysctl_ctx,
            SYSCTL_CHILDREN(oid),
            OID_AUTO,
            "model",
            CTLFLAG_RD | CTLFLAG_DYN,
            (void *) nv, 0,
            nvidia_sysctl_dev_model,
            "A",
            "NVIDIA Device Model Name");

    SYSCTL_ADD_UINT(&sc->sysctl_ctx,
            SYSCTL_CHILDREN(oid),
            OID_AUTO,
            "irq",
            CTLFLAG_RD | CTLFLAG_DYN,
            &nv->interrupt_line,
            0,
            "NVIDIA Device IRQ Number");

    SYSCTL_ADD_PROC(&sc->sysctl_ctx,
            SYSCTL_CHILDREN(oid),
            OID_AUTO,
            "vbios",
            CTLFLAG_RD | CTLFLAG_DYN,
            (void *) nv, 0,
            nvidia_sysctl_dev_vbios,
            "A",
            "NVIDIA Device VBIOS Version");

    SYSCTL_ADD_PROC(&sc->sysctl_ctx,
            SYSCTL_CHILDREN(oid),
            OID_AUTO,
            "type",
            CTLFLAG_RD | CTLFLAG_DYN,
            (void *) sc, 0,
            nvidia_sysctl_bus_type,
            "A",
            "NVIDIA Device Bus Type");


    if (nvidia_pci_find_capability(sc->dev, PCIR_CAP_ID_AGP) != 0) {
        /*
         * Assume this is the only AGP VGA device in the system
         * and report its capabilities (hw.nvidia.agp.card).
         */
        oid = SYSCTL_ADD_NODE(&sysctl_ctx,
                SYSCTL_CHILDREN(oid_agp),
                OID_AUTO,
                "card",
                CTLFLAG_RD | CTLFLAG_DYN,
                0,
                "NVIDIA AGP Device Node");

        SYSCTL_ADD_PROC(&sysctl_ctx,
                SYSCTL_CHILDREN(oid),
                OID_AUTO,
                "rates",
                CTLFLAG_RD | CTLFLAG_DYN,
                (void *) sc->dev, 0,
                nvidia_sysctl_agp_rates,
                "A",
                "NVIDIA AGP Device Rates Info");

        SYSCTL_ADD_PROC(&sysctl_ctx,
                SYSCTL_CHILDREN(oid),
                OID_AUTO,
                "fw",
                CTLFLAG_RD | CTLFLAG_DYN,
                (void *) sc->dev, 0,
                nvidia_sysctl_agp_fw,
                "A",
                "NVIDIA AGP Device FW Info");

        SYSCTL_ADD_PROC(&sysctl_ctx,
                SYSCTL_CHILDREN(oid),
                OID_AUTO,
                "sba",
                CTLFLAG_RD | CTLFLAG_DYN,
                (void *) sc->dev, 0,
                nvidia_sysctl_agp_sba,
                "A",
                "NVIDIA AGP Device SBA Info");

        SYSCTL_ADD_PROC(&sysctl_ctx,
                SYSCTL_CHILDREN(oid),
                OID_AUTO,
                "registers",
                CTLFLAG_RD | CTLFLAG_DYN,
                (void *) sc->dev, 0,
                nvidia_sysctl_agp_registers,
                "A",
                "NVIDIA AGP Device Registers");


        if (nvidia_find_bridge() != NULL) {
            /*
             * If we can find a bridge and what assume to be the
             * only AGP VGA device, report the AGP status.
             */
            oid = SYSCTL_ADD_NODE(&sysctl_ctx,
                    SYSCTL_CHILDREN(oid_agp),
                    OID_AUTO,
                    "status",
                    CTLFLAG_RD | CTLFLAG_DYN,
                    0,
                    "NVIDIA AGP Status Node");

            SYSCTL_ADD_PROC(&sysctl_ctx,
                    SYSCTL_CHILDREN(oid),
                    OID_AUTO,
                    "status",
                    CTLFLAG_RD | CTLFLAG_DYN,
                    (void *) sc->dev, 0,
                    nvidia_sysctl_agp_status,
                    "A",
                    "NVIDIA AGP Status Information");

            SYSCTL_ADD_PROC(&sysctl_ctx,
                    SYSCTL_CHILDREN(oid),
                    OID_AUTO,
                    "driver",
                    CTLFLAG_RD | CTLFLAG_DYN,
                    (void *) nv, 0,
                    nvidia_sysctl_agp_driver,
                    "A",
                    "NVIDIA AGP Driver Information");

            SYSCTL_ADD_PROC(&sysctl_ctx,
                    SYSCTL_CHILDREN(oid),
                    OID_AUTO,
                    "rate",
                    CTLFLAG_RD | CTLFLAG_DYN,
                    (void *) sc->dev, 0,
                    nvidia_sysctl_agp_rate_status,
                    "A",
                    "NVIDIA AGP Rate Status Information");

            SYSCTL_ADD_PROC(&sysctl_ctx,
                    SYSCTL_CHILDREN(oid),
                    OID_AUTO,
                    "fw",
                    CTLFLAG_RD | CTLFLAG_DYN,
                    (void *) sc->dev, 0,
                    nvidia_sysctl_agp_fw_status,
                    "A",
                    "NVIDIA AGP FW Status Information");

            SYSCTL_ADD_PROC(&sysctl_ctx,
                    SYSCTL_CHILDREN(oid),
                    OID_AUTO,
                    "sba",
                    CTLFLAG_RD | CTLFLAG_DYN,
                    (void *) sc->dev, 0,
                    nvidia_sysctl_agp_sba_status,
                    "A",
                    "NVIDIA AGP SBA Status Information");
        }
    }
}

void nv_sysctl_exit(nv_state_t *nv)
{
    struct nvidia_softc *sc = nv->os_state;
    sysctl_ctx_free(&sc->sysctl_ctx);
}

