/*
 * Copyright (c) 2020-2021 Huawei Device Co., Ltd. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this list of
 *    conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
 *    of conditions and the following disclaimer in the documentation and/or other materials
 *    provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used
 *    to endorse or promote products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <linux/fs.h>
#include "hdf_log.h"
#include "hdf_io_service.h"
#include "osal_cdev.h"
#include "osal_file.h"
#include "osal_mem.h"
#include "osal_uaccess.h"

#define HDF_LOG_TAG osal_cdev

struct OsalCdev {
    struct file_operations_vfs fops;
    const struct OsalCdevOps* opsImpl;
    const char* path;
    void* priv;
};

int OsalCdevOpen(struct file* filep)
{
    struct drv_data* drvData = (struct drv_data* )filep->f_vnode->data;
    struct OsalCdev* dev = (struct OsalCdev* )drvData->priv;
    return dev->opsImpl->open(dev, filep);
}

int OsalCdevRelease(struct file* filep)
{
    if (filep == NULL || filep->f_vnode == NULL) {
        return HDF_ERR_INVALID_OBJECT;
    }
    struct drv_data* drvData = (struct drv_data* )filep->f_vnode->data;
    struct OsalCdev* dev = (struct OsalCdev* )drvData->priv;
    return dev->opsImpl->release(dev, filep);
}

ssize_t OsalCdevRead(struct file* filep, char* buffer, size_t buflen)
{
    if (filep == NULL || filep->f_vnode == NULL) {
        return HDF_ERR_INVALID_OBJECT;
    }
    struct drv_data* drvData = (struct drv_data* )filep->f_vnode->data;
    struct OsalCdev* dev = (struct OsalCdev* )drvData->priv;
    return dev->opsImpl->read(filep, buffer, buflen, 0);
}

ssize_t OsalCdevWrite(struct file* filep, const char* buffer, size_t buflen)
{
    if (filep == NULL || filep->f_vnode == NULL) {
        return HDF_ERR_INVALID_OBJECT;
    }
    struct drv_data* drvData = (struct drv_data* )filep->f_vnode->data;
    struct OsalCdev* dev = (struct OsalCdev* )drvData->priv;
    return dev->opsImpl->write(filep, buffer, buflen, 0);
}

off_t OsalCdevSeek(struct file* filep, off_t offset, int whence)
{
    if (filep == NULL || filep->f_vnode == NULL) {
        return HDF_ERR_INVALID_OBJECT;
    }
    struct drv_data* drvData = (struct drv_data* )filep->f_vnode->data;
    struct OsalCdev* dev = (struct OsalCdev* )drvData->priv;
    return dev->opsImpl->seek(filep, offset, whence);
}

int OsalCdevIoctl(struct file* filep, int cmd, unsigned long arg)
{
    if (filep == NULL || filep->f_vnode == NULL) {
        return HDF_ERR_INVALID_OBJECT;
    }
    struct drv_data* drvData = (struct drv_data* )filep->f_vnode->data;
    struct OsalCdev* dev = (struct OsalCdev* )drvData->priv;
    return dev->opsImpl->ioctl(filep, cmd, arg);
}

int OsalCdevPoll(struct file* filep, poll_table* fds)
{
    if (filep == NULL || filep->f_vnode == NULL) {
        return HDF_ERR_INVALID_OBJECT;
    }
    struct drv_data* drvData = (struct drv_data* )filep->f_vnode->data;
    struct OsalCdev* dev = (struct OsalCdev* )drvData->priv;
    return dev->opsImpl->poll(filep, fds);
}

static void AssignFileOps(struct file_operations_vfs* fops, const struct OsalCdevOps* src)
{
    fops->seek = src->seek != NULL ? OsalCdevSeek : NULL;
    fops->read = src->read != NULL ? OsalCdevRead : NULL;
    fops->write = src->write != NULL ? OsalCdevWrite : NULL;
    fops->poll = src->poll != NULL ? OsalCdevPoll : NULL;
    fops->ioctl = src->ioctl != NULL ? OsalCdevIoctl : NULL;
    fops->open = src->open != NULL ? OsalCdevOpen : NULL;
    fops->close = src->release != NULL ? OsalCdevRelease : NULL;
}

struct OsalCdev* OsalAllocCdev(const struct OsalCdevOps* fops)
{
    struct OsalCdev* cdev = OsalMemCalloc(sizeof(struct OsalCdev));
    if (cdev == NULL) {
        return NULL;
    }

    AssignFileOps(&cdev->fops, fops);
    cdev->opsImpl = fops;

    return cdev;
}

int OsalRegisterCdev(struct OsalCdev* cdev, const char* name, unsigned int mode, void* priv)
{
    if (cdev == NULL || name == NULL) {
        return HDF_ERR_INVALID_PARAM;
    }
    HDF_LOGI("%s:register %s", __func__, name);
    static bool devPathInitted = false;
    if (!devPathInitted && mkdir(DEV_NODE_PATH, DEV_NODE_PATH_MODE) == 0) {
        devPathInitted = true;
    }
    int ret =  register_driver(name, &cdev->fops, mode, cdev);
    if (ret == HDF_SUCCESS) {
        cdev->priv = priv;
        cdev->path = name;
    }
    return ret;
}

void OsalUnregisterCdev(struct OsalCdev* cdev)
{
    if (cdev != NULL) {
        unregister_driver(cdev->path);
    }
}

void OsalFreeCdev(struct OsalCdev* cdev)
{
    if (cdev != NULL) {
        OsalMemFree(cdev);
    }
}

void* OsalGetCdevPriv(struct OsalCdev* cdev)
{
    return cdev != NULL ? cdev->priv : NULL;
}

void OsalSetFilePriv(struct file* filep, void* priv)
{
    if (filep != NULL) {
        filep->f_priv = priv;
    }
}

void* OsalGetFilePriv(struct file* filep)
{
    return filep != NULL ? filep->f_priv : NULL;
}