* 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 "i2c_dev.h"
#include <fs/driver.h>
#include "i2c_core.h"
#include "osal_mem.h"
#include "user_copy.h"
#define I2C_FS_MODE 0660
#define I2C_RDWR_IOCTL_MAX_MSGS 42
#define I2C_BUF_MAX 8192
#define I2C_NAME_SIZE 32
#define I2C_CNTLR_MAX 32
struct I2cClient {
DevHandle handle;
struct I2cHost *host;
uint16_t addr;
uint16_t flags;
};
static inline int32_t I2cMsgInitFromUser(struct I2cMsg *kMsgs, struct i2c_msg *uMsgs)
{
int32_t ret;
kMsgs->len = uMsgs->len;
kMsgs->addr = uMsgs->addr;
kMsgs->flags = uMsgs->flags;
if ((uMsgs->flags & I2C_M_RD) != 0) {
return HDF_SUCCESS;
}
ret = LOS_CopyToKernel((void *)(kMsgs->buf), kMsgs->len, (void *)(uMsgs->buf), uMsgs->len);
return (ret == LOS_OK) ? HDF_SUCCESS : HDF_ERR_IO;
}
static inline int32_t I2cMsgBackToUser(struct I2cMsg *kMsgs, struct i2c_msg *uMsgs)
{
int32_t ret;
if ((kMsgs->flags & I2C_M_RD) == 0) {
return HDF_SUCCESS;
}
ret = LOS_CopyFromKernel((void *)(uMsgs->buf), uMsgs->len, (void *)(kMsgs->buf), kMsgs->len);
return (ret == LOS_OK) ? HDF_SUCCESS : HDF_ERR_IO;
}
static inline int32_t I2cMsgsCopyBackToUser(I2cIoctlWrap *wrap,
struct I2cMsg *kMsgs, struct i2c_msg *uMsgs)
{
int ret;
unsigned int i;
for (i = 0; i < wrap->nmsgs; i++) {
ret = I2cMsgBackToUser(&kMsgs[i], &uMsgs[i]);
if (ret != HDF_SUCCESS) {
break;
}
}
return ret;
}
static void I2cMsgsDestroy(struct I2cMsg *kMsgs, struct i2c_msg *uMsgs)
{
if (kMsgs != NULL) {
if (kMsgs[0].buf != NULL) {
OsalMemFree(kMsgs[0].buf);
kMsgs[0].buf = NULL;
}
}
if (uMsgs != NULL) {
OsalMemFree(uMsgs);
}
}
static int32_t I2cMsgsCreateFromUser(I2cIoctlWrap *wrap,
struct I2cMsg **kMsgsPp, struct i2c_msg **uMsgsPp)
{
int32_t ret;
size_t i;
size_t bufLen;
struct i2c_msg *uMsgs = NULL;
struct I2cMsg *kMsgs = NULL;
uint8_t *dataBuf = NULL;
uMsgs = (struct i2c_msg *)OsalMemCalloc((sizeof(*uMsgs) + sizeof(*kMsgs)) * wrap->nmsgs);
if (uMsgs == NULL) {
return HDF_ERR_MALLOC_FAIL;
}
kMsgs = (struct I2cMsg *)((uint8_t *)uMsgs + sizeof(*uMsgs) * wrap->nmsgs);
ret = LOS_CopyToKernel((void *)uMsgs, sizeof(*uMsgs) * wrap->nmsgs,
(void *)wrap->msgs, sizeof(*uMsgs) * wrap->nmsgs);
if (ret != LOS_OK) {
HDF_LOGE("%s: copy msgs from user fail!", __func__);
goto __ERR__;
}
for (i = 0, bufLen = 0; i < wrap->nmsgs; i++) {
if (uMsgs[i].buf == NULL || uMsgs[i].len == 0) {
ret = HDF_ERR_INVALID_PARAM;
goto __ERR__;
}
bufLen += uMsgs[i].len;
}
if (bufLen >= I2C_BUF_MAX) {
HDF_LOGE("%s: buf too long:%u", __func__, bufLen);
ret = HDF_ERR_INVALID_PARAM;
goto __ERR__;
}
dataBuf = (uint8_t *)OsalMemCalloc(bufLen);
if (dataBuf == NULL) {
ret = HDF_ERR_MALLOC_FAIL;
goto __ERR__;
}
for (i = 0; i < wrap->nmsgs; i++) {
kMsgs[i].buf = dataBuf;
dataBuf += uMsgs[i].len;
ret = I2cMsgInitFromUser(&kMsgs[i], &uMsgs[i]);
if (ret != HDF_SUCCESS) {
goto __ERR__;
}
}
*kMsgsPp = kMsgs;
*uMsgsPp = uMsgs;
return HDF_SUCCESS;
__ERR__:
I2cMsgsDestroy(kMsgs, uMsgs);
return ret;
}
static int32_t I2cCntlrRead(DevHandle handle, uint16_t addr,
uint8_t *buf, int16_t len, uint16_t flags)
{
int32_t ret;
struct I2cMsg msg;
msg.addr = addr;
msg.buf = buf;
msg.len = (uint16_t)len;
msg.flags = flags | I2C_FLAG_READ;
ret = I2cTransfer(handle, &msg, 1);
return (ret == 1) ? HDF_SUCCESS : ret;
}
ssize_t I2cFsRead(struct file *filep, char *buf, size_t count)
{
int32_t ret;
uint8_t *kbuf = NULL;
struct I2cClient *client = filep->f_priv;
if (client == NULL) {
HDF_LOGE("%s: client is null!", __func__);
return 0;
}
kbuf = (uint8_t *)OsalMemCalloc(count);
if (kbuf == NULL) {
HDF_LOGE("%s: malloc kbuf fail!", __func__);
return 0;
}
ret = I2cCntlrRead(client->handle, client->addr, kbuf, count, client->flags);
PLAT_LOGV("%s: I2cRead called, ret:%d", __func__, ret);
if (ret == HDF_SUCCESS) {
if (LOS_CopyFromKernel(buf, count, kbuf, count) != LOS_OK) {
HDF_LOGE("%s: copy from kernel fail:%d", __func__, ret);
ret = HDF_ERR_IO;
}
}
OsalMemFree(kbuf);
return (ret == HDF_SUCCESS) ? count : 0;
}
static int32_t I2cCntlrWrite(DevHandle handle, uint16_t addr,
const uint8_t *buf, int16_t len, uint16_t flags)
{
int32_t ret;
struct I2cMsg msg;
msg.addr = addr;
msg.buf = (uint8_t *)buf;
msg.len = (uint16_t)len;
msg.flags = flags & (~I2C_FLAG_READ);
ret = I2cTransfer(handle, &msg, 1);
return (ret == 1) ? HDF_SUCCESS : ret;
}
ssize_t I2cFsWrite(struct file *filep, const char *buf, size_t count)
{
int32_t ret;
uint8_t *kbuf = NULL;
struct I2cClient *client = filep->f_priv;
if (client == NULL) {
HDF_LOGE("%s: client is null!", __func__);
return 0;
}
kbuf = (uint8_t *)OsalMemCalloc(count);
if (kbuf == NULL) {
HDF_LOGE("%s: malloc kbuf fail!", __func__);
return 0;
}
if (LOS_CopyToKernel(kbuf, count, buf, count) != LOS_OK) {
HDF_LOGE("%s: copy to kernel fail!", __func__);
OsalMemFree(kbuf);
return 0;
}
ret = I2cCntlrWrite(client->handle, client->addr, kbuf, count, client->flags);
PLAT_LOGV("%s: I2cWrite called, ret:%d", __func__, ret);
OsalMemFree(kbuf);
return (ret == HDF_SUCCESS) ? count : 0;
}
static int I2cIoctlReadWrite(const struct I2cClient *client, const void *arg)
{
int ret;
I2cIoctlWrap wrap;
struct i2c_msg *uMsgs = NULL;
struct I2cMsg *kMsgs = NULL;
if (arg == NULL) {
HDF_LOGE("%s: arg is null!", __func__);
return HDF_ERR_INVALID_PARAM;
}
ret = LOS_CopyToKernel(&wrap, sizeof(wrap), (void *)arg, sizeof(wrap));
if (ret != LOS_OK) {
HDF_LOGE("%s: copy wrap fail!", __func__);
return HDF_ERR_IO;
}
if (wrap.msgs == NULL || wrap.nmsgs == 0 || wrap.nmsgs > I2C_RDWR_IOCTL_MAX_MSGS) {
HDF_LOGE("%s: wrap msgs is null or invalid num:%u!", __func__, wrap.nmsgs);
return HDF_ERR_INVALID_PARAM;
}
ret = I2cMsgsCreateFromUser(&wrap, &kMsgs, &uMsgs);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: recreate msgs fail!", __func__);
return ret;
}
ret = I2cTransfer(client->handle, kMsgs, wrap.nmsgs);
PLAT_LOGV("%s: I2cTransfer called, ret:%d", __func__, ret);
if ((unsigned int)ret == wrap.nmsgs) {
ret = I2cMsgsCopyBackToUser(&wrap, kMsgs, uMsgs);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: copy back fail! ret:%d", __func__, ret);
}
} else {
HDF_LOGE("%s: transfer fail, ret:%d, nmsgs:%u", __func__, ret, wrap.nmsgs);
}
I2cMsgsDestroy(kMsgs, uMsgs);
return ret;
}
static int I2cFsIoctl(struct file *filep, int cmd, unsigned long arg)
{
int retval = ENOERR;
struct I2cClient *client = filep->f_priv;
switch (cmd) {
case IOCTL_CLIENT_FORCE:
case IOCTL_CLIENT:
if ((((client->flags & I2C_M_TEN) == 0) && arg > 0xfe) || (arg > 0x3ff)) {
HDF_LOGE("%s:Not support arg(%0lu)!!!", __func__, arg);
retval = -EINVAL;
break;
}
client->addr = arg;
break;
case IOCTL_RDWR: {
retval = I2cIoctlReadWrite(client, (void *)(uintptr_t)arg);
break;
}
case IOCTL_16BIT_REG:
if (arg == 0) {
client->flags &= ~I2C_M_16BIT_REG;
} else {
client->flags |= I2C_M_16BIT_REG;
}
break;
case IOCTL_16BIT_DATA:
if (arg == 0) {
client->flags &= ~I2C_M_16BIT_DATA;
} else {
client->flags |= I2C_M_16BIT_DATA;
}
break;
case IOCTL_TENBIT:
if (arg == 0) {
client->flags &= ~I2C_M_TEN;
} else {
client->flags |= I2C_M_TEN;
}
break;
case IOCTL_PEC:
case IOCTL_RETRIES:
case IOCTL_TIMEOUT:
default:
HDF_LOGE("Not support cmd(%0d)!!!", cmd);
retval = -EINVAL;
}
return retval;
}
static int I2cFsOpen(struct file *filep)
{
DevHandle handle = NULL;
struct I2cClient *client = NULL;
struct drv_data *drvData = NULL;
int16_t id;
if (filep == NULL || filep->f_vnode == NULL || filep->f_vnode->data == NULL) {
HDF_LOGE("%s: function parameter is null", __func__);
return -EINVAL;
}
drvData = (struct drv_data *)filep->f_vnode->data;
id = (int16_t)(uintptr_t)drvData->priv;
handle = I2cOpen(id);
if (handle == NULL) {
HDF_LOGE("%s:Fail to get host:%d handle!", __func__, id);
return -1;
}
client = (struct I2cClient *)OsalMemCalloc(sizeof(*client));
if (client == NULL) {
HDF_LOGE("%s:Fail to malloc client-%d!", __func__, id);
return -1;
}
client->handle = handle;
filep->f_priv = client;
return 0;
}
static int I2cFsClose(struct file *filep)
{
struct I2cClient *client = filep->f_priv;
if (client == NULL) {
HDF_LOGE("%s: has't opened!", __func__);
return 0;
}
if (client->handle == NULL) {
return 0;
}
I2cClose(client->handle);
client->handle = NULL;
OsalMemFree(client);
client = NULL;
filep->f_priv = NULL;
return 0;
}
static ssize_t I2cFsMap(struct file* filep, LosVmMapRegion *region)
{
size_t size = region->range.size;
(void)filep;
PADDR_T paddr = region->pgOff << PAGE_SHIFT;
VADDR_T vaddr = region->range.base;
LosVmSpace *space = LOS_SpaceGet(vaddr);
if ((space == NULL) || ((paddr >= SYS_MEM_BASE) && (paddr < SYS_MEM_END))) {
return -EINVAL;
}
if (LOS_ArchMmuMap(&space->archMmu, vaddr, paddr, size >> PAGE_SHIFT, region->regionFlags) <= 0) {
return -EAGAIN;
}
return 0;
}
static const struct file_operations_vfs g_i2cFops = {
.open = I2cFsOpen,
.close = I2cFsClose,
.read = I2cFsRead,
.write = I2cFsWrite,
.ioctl = I2cFsIoctl,
.mmap = I2cFsMap,
};
int32_t I2cAddVfsById(int16_t id)
{
#ifdef LOSCFG_FS_VFS
int32_t ret;
char *name = NULL;
if (id < 0 || id >= I2C_CNTLR_MAX) {
HDF_LOGE("%s: id:%d exceed max:%d", __func__, id, I2C_CNTLR_MAX);
return HDF_ERR_INVALID_PARAM;
}
name = (char *)OsalMemCalloc(I2C_NAME_SIZE);
if (name == NULL) {
HDF_LOGE("%s: malloc name fail!", __func__);
return HDF_ERR_MALLOC_FAIL;
}
ret = snprintf_s(name, I2C_NAME_SIZE, I2C_NAME_SIZE - 1, "/dev/i2c-%d", id);
if (ret < 0) {
HDF_LOGE("%s: format name fail! ret:%d", __func__, ret);
OsalMemFree(name);
return ret;
}
ret = register_driver(name, &g_i2cFops, I2C_FS_MODE, (void *)((uintptr_t)id));
if (ret != 0) {
HDF_LOGE("%s: register %s fail! ret:%d", __func__, name, ret);
}
OsalMemFree(name);
return ret;
#else
(void)id;
return HDF_SUCCESS;
#endif
}
void I2cRemoveVfsById(int16_t id)
{
#ifdef LOSCFG_FS_VFS
int32_t ret;
char *name = NULL;
if (id < 0 || id >= I2C_CNTLR_MAX) {
HDF_LOGE("%s: id:%d exceed max:%d", __func__, id, I2C_CNTLR_MAX);
return;
}
name = (char *)OsalMemCalloc(I2C_NAME_SIZE);
if (name == NULL) {
HDF_LOGE("%s: malloc name fail!", __func__);
return;
}
ret = snprintf_s(name, I2C_NAME_SIZE, I2C_NAME_SIZE - 1, "/dev/i2c-%d", id);
if (ret < 0) {
HDF_LOGE("%s: format name fail! ret:%d", __func__, ret);
OsalMemFree(name);
return;
}
ret = unregister_driver(name);
if (ret != 0) {
HDF_LOGE("%s: unregister %s fail!", __func__, name);
}
OsalMemFree(name);
#else
(void)id;
#endif
}