/* _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"

static d_open_t  nvidia_ctl_open;
static d_close_t nvidia_ctl_close;
static d_ioctl_t nvidia_ctl_ioctl;
static d_poll_t  nvidia_ctl_poll;

static struct cdevsw nvidia_ctl_cdevsw = {
    .d_open =      nvidia_ctl_open,
    .d_close =     nvidia_ctl_close,
    .d_ioctl =     nvidia_ctl_ioctl,
    .d_poll =      nvidia_ctl_poll,
    .d_name =      "nvidiactl",
    .d_version =   D_VERSION,
    .d_flags =     D_TRACKCLOSE|D_NEEDGIANT
};

static struct cdev *nvidia_ctl_cdev = NULL;
struct nvidia_softc nvidia_ctl_sc;

static int nvidia_count = 0;

int nvidia_ctl_open(
    struct cdev *dev,
    int oflags,
    int devtype,
    d_thread_t *td
)
{
    int status;
    nv_state_t *nv = &nvidia_ctl_state;

    nv_lock_api(nv);
    status = nvidia_open_ctl(dev, td);
    nv_unlock_api(nv);

    return status;
}

int nvidia_ctl_close(
    struct cdev *dev,
    int fflag,
    int devtype,
    d_thread_t *td
)
{
    int status;
    nv_state_t *nv = &nvidia_ctl_state;

    nv_lock_api(nv);
    status = nvidia_close_ctl(dev, td);
    nv_unlock_api(nv);

    return status;
}

int nvidia_ctl_ioctl(
    struct cdev *dev,
    u_long cmd,
    caddr_t data,
    int fflag,
    d_thread_t *td
)
{
    int status = 0;
    nv_state_t *nv = &nvidia_ctl_state;
    struct nvidia_softc *sc;

    sc = nv->os_state;

    if (__NV_IOC_TYPE(cmd) != NV_IOCTL_MAGIC)
        return ENOTTY;

    nv_lock_api(nv);
    status = nvidia_handle_ioctl(dev, cmd, data, fflag, td);
    nv_unlock_api(nv);

    return status;
}

int nvidia_ctl_poll(
    struct cdev *dev,
    int events,
    d_thread_t *td
)
{
    nv_state_t *nv;
    struct nvidia_softc *sc;
    struct nvidia_event *et;

    nv = &nvidia_ctl_state;
    sc = nv->os_state;

    nv_lock_rm(nv);

    STAILQ_FOREACH(et, &sc->event_queue, queue) {
        if (et->event.file == __TD_FDT(td))
            break;
    }

    if (et == NULL) {
        nv_unlock_rm(nv);
        selrecord(td, &sc->rsel);
    } else {
        nv_unlock_rm(nv);
        return (events & (POLLIN | POLLPRI | POLLRDNORM));
    }

    return 0;
}

int nvidia_ctl_attach(void)
{
    if (nvidia_count == 0) {
        /*
         * This routine is called from nvidia_attach(), multiple times
         * when more than one device is installed.
         */
        nvidia_ctl_cdev = make_dev(&nvidia_ctl_cdevsw,
                CDEV_CTL_MINOR,
                UID_ROOT, GID_WHEEL, 0666,
                "%s", nvidia_ctl_cdevsw.d_name);
    }

    nvidia_count++;
    return 0;
}

int nvidia_ctl_detach(void)
{
    struct nvidia_softc *sc;

    sc = &nvidia_ctl_sc;
    nvidia_count--;

    if (nvidia_count == 0) {
        /*
         * Like nvidia_ctl_attach(), nvidia_ctl_detach() will also be
         * called more than once with multiple devices.
         */
        destroy_dev(nvidia_ctl_cdev);
    }

    return 0;
}
