Files
mr-library/source/device.c
2024-01-19 20:42:34 +08:00

859 lines
21 KiB
C

/*
* @copyright (c) 2023-2024, MR Development Team
*
* @license SPDX-License-Identifier: Apache-2.0
*
* @date 2023-10-20 MacRsh First version
*/
#include "include/mr_api.h"
#define MR_ROOT_DEV_NAME "dev"
static struct mr_dev root_dev =
{
MR_MAGIC_NUMBER,
MR_ROOT_DEV_NAME,
Mr_Dev_Type_Root,
MR_NULL,
{&root_dev.list, &root_dev.list},
{&root_dev.clist, &root_dev.clist}
};
MR_INLINE int dev_is_root(struct mr_dev *dev)
{
return (int)dev->type == Mr_Dev_Type_Root;
}
MR_INLINE struct mr_dev *dev_find_child(struct mr_dev *parent, const char *name)
{
/* Find the child device */
for (struct mr_list *list = parent->clist.next; list != &parent->clist; list = list->next)
{
struct mr_dev *dev = (struct mr_dev *)MR_CONTAINER_OF(list, struct mr_dev, list);
if (strncmp(name, dev->name, MR_CFG_DEV_NAME_MAX) == 0)
{
return dev;
}
}
return MR_NULL;
}
MR_INLINE int dev_register_child(struct mr_dev *parent, struct mr_dev *child, const char *name)
{
/* Check whether the device with the same name exists */
if (dev_find_child(parent, name) != MR_NULL)
{
return MR_EEXIST;
}
/* Insert the device into the child list */
child->magic = MR_MAGIC_NUMBER;
strncpy(child->name, name, MR_CFG_DEV_NAME_MAX);
child->parent = parent;
mr_list_insert_before(&parent->clist, &child->list);
return MR_EOK;
}
MR_INLINE const char *dev_clear_path(const char *path)
{
/* Skip the leading '/' */
if (*path == '/')
{
path++;
}
/* Skip the leading 'dev/'(root path) */
if (strncmp(path, MR_ROOT_DEV_NAME"/", sizeof(MR_ROOT_DEV_NAME"/") - 1) == 0)
{
path += sizeof(MR_ROOT_DEV_NAME"/") - 1;
}
return path;
}
MR_INLINE int dev_register_by_path(struct mr_dev *parent, struct mr_dev *dev, const char *path)
{
char child_name[MR_CFG_DEV_NAME_MAX + 1] = {0};
/* Clear the path */
path = dev_clear_path(path);
/* Check whether the child path exists */
const char *child_path = strchr(path, '/');
if (child_path != MR_NULL)
{
/* Get the child name */
size_t len = (child_path - path) > MR_CFG_DEV_NAME_MAX ? MR_CFG_DEV_NAME_MAX : (child_path - path);
strncpy(child_name, path, len);
child_name[len] = '\0';
/* Find the child */
struct mr_dev *child = dev_find_child(parent, child_name);
if (child == MR_NULL)
{
return MR_ENOTFOUND;
}
/* Continue iterating */
return dev_register_by_path(child, dev, child_path);
} else
{
/* Register the child device to the parent */
return dev_register_child(parent, dev, path);
}
}
MR_INLINE struct mr_dev *dev_find_by_path(struct mr_dev *parent, const char *path)
{
char child_name[MR_CFG_DEV_NAME_MAX + 1] = {0};
/* Clear the path */
path = dev_clear_path(path);
/* Check whether the child path exists */
const char *child_path = strchr(path, '/');
if (child_path != MR_NULL)
{
/* Get the child name */
size_t len = (child_path - path) > MR_CFG_DEV_NAME_MAX ? MR_CFG_DEV_NAME_MAX : (child_path - path);
strncpy(child_name, path, len);
child_name[len] = '\0';
/* Find the child */
struct mr_dev *child = dev_find_child(parent, child_name);
if (child == MR_NULL)
{
return MR_NULL;
}
/* Continue iterating */
return dev_find_by_path(child, child_path);
} else
{
/* Find the child */
return dev_find_child(parent, path);
}
}
#ifdef MR_USING_RDWR_CTL
MR_INLINE int dev_lock_take(struct mr_dev *dev, int take, int set)
{
/* Continue iterating until reach the root device */
if (dev_is_root(dev->parent) != MR_TRUE)
{
int ret = dev_lock_take(dev->parent, take, set);
if (ret < 0)
{
return ret;
}
}
/* Check whether the device is taken */
if (dev->lflags & take)
{
return MR_EBUSY;
}
/* Take the device */
MR_BIT_SET(dev->lflags, set);
return MR_EOK;
}
MR_INLINE void dev_lock_release(struct mr_dev *dev, int release)
{
/* Continue iterating until reach the root device */
if (dev_is_root(dev->parent) != MR_TRUE)
{
dev_lock_release(dev->parent, release);
}
/* Release the device */
MR_BIT_CLR(dev->lflags, release);
}
#endif /* MR_USING_RDWR_CTL */
MR_INLINE int dev_get_path(struct mr_dev *dev, char *buf, size_t bufsz)
{
int ret = 0;
/* Continue to get the path of the parent device */
if (dev->parent != MR_NULL)
{
ret = dev_get_path(dev->parent, buf, bufsz);
if (ret < 0)
{
return ret;
}
}
/* Check whether the buffer is enough */
if ((bufsz - ret) <= (strlen(dev->name) + 1))
{
return ret;
}
ret += snprintf(buf + ret, bufsz - ret, "/%s", dev->name);
return ret;
}
MR_INLINE int dev_register(struct mr_dev *dev, const char *path)
{
/* Disable interrupt */
mr_interrupt_disable();
/* Register the device to the root device */
int ret = dev_register_by_path(&root_dev, dev, path);
/* Enable interrupt */
mr_interrupt_enable();
return ret;
}
MR_INLINE struct mr_dev *dev_find(const char *path)
{
/* Find the device from the root device */
return dev_find_by_path(&root_dev, path);
}
MR_INLINE int dev_open(struct mr_dev *dev, int oflags)
{
#ifdef MR_USING_RDWR_CTL
if (MR_BIT_IS_SET(dev->sflags, oflags) != MR_ENABLE)
{
return MR_ENOTSUP;
}
#endif /* MR_USING_RDWR_CTL */
/* Check whether the device is opened */
if (dev->ref_count == 0)
{
/* Continue iterating until reach the root device */
if (dev_is_root(dev->parent) != MR_TRUE)
{
int ret = dev_open(dev->parent, oflags);
if (ret < 0)
{
return ret;
}
}
/* Open the device */
if (dev->ops->open != MR_NULL)
{
int ret = dev->ops->open(dev);
if (ret < 0)
{
return ret;
}
}
}
#ifdef MR_USING_RDWR_CTL
else if (MR_BIT_IS_SET(dev->sflags, MR_SFLAG_ONLY) == MR_ENABLE)
{
return MR_EBUSY;
}
#endif /* MR_USING_RDWR_CTL */
/* Increase the reference count */
dev->ref_count++;
return MR_EOK;
}
MR_INLINE int dev_close(struct mr_dev *dev)
{
/* Decrease the reference count */
dev->ref_count--;
/* Check whether the device needs to be closed */
if (dev->ref_count == 0)
{
/* Continue iterating until reach the root device */
if (dev_is_root(dev->parent) != MR_TRUE)
{
int ret = dev_close(dev->parent);
if (ret < 0)
{
return ret;
}
}
/* Close the device */
if (dev->ops->close != MR_NULL)
{
return dev->ops->close(dev);
}
}
return MR_EOK;
}
MR_INLINE ssize_t dev_read(struct mr_dev *dev, int off, void *buf, size_t size, int async)
{
#ifdef MR_USING_RDWR_CTL
do
{
/* Disable interrupt */
mr_interrupt_disable();
int ret = dev_lock_take(dev, (MR_LFLAG_RD | MR_LFLAG_SLEEP), MR_LFLAG_RD);
if (ret < 0)
{
/* Enable interrupt */
mr_interrupt_enable();
return ret;
}
/* Enable interrupt */
mr_interrupt_enable();
} while (0);
#endif /* MR_USING_RDWR_CTL */
/* Read buffer from the device */
ssize_t ret = dev->ops->read(dev, off, buf, size, async);
#ifdef MR_USING_RDWR_CTL
dev_lock_release(dev, MR_LFLAG_RD);
#endif /* MR_USING_RDWR_CTL */
return ret;
}
MR_INLINE ssize_t dev_write(struct mr_dev *dev, int offset, const void *buf, size_t size, int async)
{
#ifdef MR_USING_RDWR_CTL
do
{
/* Disable interrupt */
mr_interrupt_disable();
int ret = dev_lock_take(dev,
(MR_LFLAG_WR | MR_LFLAG_SLEEP | (async == MR_SYNC ? MR_LFLAG_NONBLOCK : 0)),
MR_LFLAG_WR);
if (ret < 0)
{
/* Enable interrupt */
mr_interrupt_enable();
return ret;
}
/* Enable interrupt */
mr_interrupt_enable();
} while (0);
#endif /* MR_USING_RDWR_CTL */
/* Write buffer to the device */
ssize_t ret = dev->ops->write(dev, offset, buf, size, async);
#ifdef MR_USING_RDWR_CTL
dev_lock_release(dev, MR_LFLAG_WR);
if ((async == MR_ASYNC) && (ret > 0))
{
/* Disable interrupt */
mr_interrupt_disable();
dev_lock_take(dev, 0, MR_LFLAG_NONBLOCK);
/* Enable interrupt */
mr_interrupt_enable();
}
#endif /* MR_USING_RDWR_CTL */
return ret;
}
MR_INLINE int dev_ioctl(struct mr_dev *dev, int desc, int off, int cmd, void *args)
{
/* Check whether the device has an ioctl function */
if (dev->ops->ioctl == MR_NULL)
{
return MR_ENOTSUP;
}
switch (cmd)
{
case MR_CTL_SET_RD_CALL:
{
dev->rd_call.desc = desc;
dev->rd_call.call = (int (*)(int desc, void *args))args;
return sizeof(dev->rd_call);
}
case MR_CTL_SET_WR_CALL:
{
dev->wr_call.desc = desc;
dev->wr_call.call = (int (*)(int desc, void *args))args;
return sizeof(dev->wr_call);
}
case MR_CTL_GET_SFLAGS:
{
if (args != MR_NULL)
{
int *sflags = (int *)args;
*sflags = dev->sflags;
return sizeof(sflags);
}
return MR_EINVAL;
}
case MR_CTL_GET_PATH:
{
if (args != MR_NULL)
{
struct
{
char *buf;
size_t bufsz;
} *path = args;
return dev_get_path(dev, path->buf, path->bufsz);
}
return MR_EINVAL;
}
case MR_CTL_GET_NAME:
{
if (args != MR_NULL)
{
char **name = (char **)args;
*name = dev->name;
return sizeof(name);
}
return MR_EINVAL;
}
case MR_CTL_GET_RD_CALL:
{
if (args != MR_NULL)
{
*(int (**)(int desc, void *args))args = dev->rd_call.call;
return sizeof(dev->rd_call.call);
}
return MR_EINVAL;
}
case MR_CTL_GET_WR_CALL:
{
if (args != MR_NULL)
{
*(int (**)(int desc, void *args))args = dev->wr_call.call;
return sizeof(dev->wr_call.call);
}
return MR_EINVAL;
}
default:
{
#ifdef MR_USING_RDWR_CTL
do
{
/* Disable interrupt */
mr_interrupt_disable();
int ret = dev_lock_take(dev, (MR_LFLAG_RDWR | MR_LFLAG_SLEEP | MR_LFLAG_NONBLOCK), MR_LFLAG_RDWR);
if (ret < 0)
{
/* Enable interrupt */
mr_interrupt_enable();
return ret;
}
/* Enable interrupt */
mr_interrupt_enable();
} while (0);
#endif /* MR_USING_RDWR_CTL */
/* I/O control to the device */
int ret = dev->ops->ioctl(dev, off, cmd, args);
#ifdef MR_USING_RDWR_CTL
dev_lock_release(dev, MR_LFLAG_RDWR);
#endif /* MR_USING_RDWR_CTL */
return ret;
}
}
}
/**
* @brief This function register a device.
*
* @param dev The device.
* @param path The path of the device.
* @param type The type of the device.
* @param sflags The support flags of the device.
* @param ops The operations of the device.
* @param drv The driver of the device.
*
* @return MR_EOK on success, otherwise an error code.
*/
int mr_dev_register(struct mr_dev *dev,
const char *path,
int type,
int sflags,
struct mr_dev_ops *ops,
struct mr_drv *drv)
{
static struct mr_dev_ops null_ops = {MR_NULL};
MR_ASSERT(dev != MR_NULL);
MR_ASSERT(dev->magic != MR_MAGIC_NUMBER);
MR_ASSERT(path != MR_NULL);
MR_ASSERT(type != Mr_Dev_Type_Root);
MR_ASSERT((ops != MR_NULL) || (sflags == MR_SFLAG_NONRDWR));
MR_ASSERT((ops->read != MR_NULL) || (MR_BIT_IS_SET(sflags, MR_SFLAG_RDONLY) == MR_DISABLE));
MR_ASSERT((ops->write != MR_NULL) || (MR_BIT_IS_SET(sflags, MR_SFLAG_WRONLY) == MR_DISABLE));
MR_ASSERT((drv != MR_NULL) || (sflags & MR_SFLAG_NONDRV));
MR_ASSERT((drv == MR_NULL) || (drv->type == type));
/* Initialize the fields */
dev->magic = 0;
memset(dev->name, '\0', MR_CFG_DEV_NAME_MAX);
dev->parent = MR_NULL;
mr_list_init(&dev->list);
mr_list_init(&dev->clist);
dev->type = type;
dev->ref_count = 0;
#ifdef MR_USING_RDWR_CTL
dev->sflags = sflags;
dev->lflags = 0;
#endif /* MR_USING_RDWR_CTL */
dev->rd_call.desc = -1;
dev->rd_call.call = MR_NULL;
dev->wr_call.desc = -1;
dev->wr_call.call = MR_NULL;
dev->ops = (ops != MR_NULL) ? ops : &null_ops;
dev->drv = drv;
/* Register the device */
return dev_register(dev, path);
}
/**
* @brief This function handle device interrupt.
*
* @param dev The device to be handle.
* @param event The event to be handle.
* @param args The arguments of the event.
*/
int mr_dev_isr(struct mr_dev *dev, int event, void *args)
{
MR_ASSERT(dev != MR_NULL);
/* Check whether the device is opened */
if (dev->ref_count == 0)
{
return MR_EINVAL;
}
/* Check whether the device has ISR */
if (dev->ops->isr == MR_NULL)
{
return MR_ENOTSUP;
}
/* Call the device ISR */
ssize_t ret = dev->ops->isr(dev, event, args);
if (ret < 0)
{
return (int)ret;
}
/* Handle the event */
switch (event & MR_ISR_MASK)
{
case MR_ISR_RD:
{
if (dev->rd_call.call != MR_NULL)
{
return dev->rd_call.call(dev->rd_call.desc, &ret);
}
return MR_EOK;
}
case MR_ISR_WR:
{
#ifdef MR_USING_RDWR_CTL
dev_lock_release(dev, MR_LFLAG_NONBLOCK);
#endif /* MR_USING_RDWR_CTL */
if (dev->wr_call.call != MR_NULL)
{
return dev->wr_call.call(dev->wr_call.desc, &ret);
}
return MR_EOK;
}
default:
{
return MR_ENOTSUP;
}
}
}
/**
* @brief Device descriptor structure.
*/
static struct mr_desc
{
struct mr_dev *dev; /* Device */
int oflags; /* Open flags */
int offset; /* Offset */
#ifndef MR_CFG_DESC_MAX
#define MR_CFG_DESC_MAX (32)
#endif /* MR_CFG_DESC_MAX */
} desc_map[MR_CFG_DESC_MAX] = {0};
#define DESC_OF(desc) (desc_map[(desc)])
#define DESC_IS_VALID(desc) (((desc) >= 0 && (desc) < MR_CFG_DESC_MAX) && ((DESC_OF(desc).dev) != MR_NULL))
#ifdef MR_USING_DESC_CHECK
#define MR_DESC_CHECK(desc) if (DESC_IS_VALID(desc) == MR_FALSE) { return MR_EINVAL; }
#else
#define MR_DESC_CHECK(desc)
#endif /* MR_USING_DESC_CHECK */
static int desc_allocate(const char *path)
{
int desc = -1;
/* Find a free descriptor */
for (size_t i = 0; i < MR_CFG_DESC_MAX; i++)
{
if (DESC_OF(i).dev == MR_NULL)
{
desc = (int)i;
break;
}
}
if (desc < 0)
{
return MR_ENOMEM;
}
/* Find the device */
struct mr_dev *dev = dev_find(path);
if (dev == MR_NULL)
{
return MR_ENOTFOUND;
}
/* Initialize the fields */
DESC_OF(desc).dev = dev;
DESC_OF(desc).offset = -1;
DESC_OF(desc).oflags = MR_OFLAG_CLOSED;
return desc;
}
static void desc_free(int desc)
{
if (desc >= 0 && desc < MR_CFG_DESC_MAX)
{
DESC_OF(desc).dev = MR_NULL;
DESC_OF(desc).oflags = MR_OFLAG_CLOSED;
DESC_OF(desc).offset = -1;
}
}
/**
* @brief This function open a device.
*
* @param path The path of the device.
* @param oflags The open flags of the device.
*
* @return The descriptor of the device, otherwise an error code.
*/
int mr_dev_open(const char *path, int oflags)
{
MR_ASSERT(path != MR_NULL);
MR_ASSERT(oflags != MR_OFLAG_CLOSED);
/* Allocate a descriptor */
int desc = desc_allocate(path);
if (desc < 0)
{
return desc;
}
/* Open the device */
int ret = dev_open(DESC_OF(desc).dev, oflags);
if (ret < 0)
{
desc_free(desc);
return ret;
}
/* Initialize the open flags */
DESC_OF(desc).oflags = oflags;
return desc;
}
/**
* @brief This function close a device.
*
* @param desc The descriptor of the device.
*
* @return MR_EOK on success, otherwise an error code.
*/
int mr_dev_close(int desc)
{
MR_DESC_CHECK(desc);
/* Close the device */
int ret = dev_close(DESC_OF(desc).dev);
if (ret < 0)
{
return ret;
}
/* Free the descriptor */
desc_free(desc);
return MR_EOK;
}
/**
* @brief This function read a device.
*
* @param desc The descriptor of the device.
* @param buf The buf buffer to be read.
* @param size The size of read.
*
* @return The size of the actual read, otherwise an error code.
*/
ssize_t mr_dev_read(int desc, void *buf, size_t size)
{
MR_ASSERT((buf != MR_NULL) || (size == 0));
MR_DESC_CHECK(desc);
#ifdef MR_USING_RDWR_CTL
if (MR_BIT_IS_SET(DESC_OF(desc).oflags, MR_OFLAG_RDONLY) == MR_DISABLE)
{
return MR_ENOTSUP;
}
#endif /* MR_USING_RDWR_CTL */
/* Read buffer from the device */
return dev_read(DESC_OF(desc).dev,
DESC_OF(desc).offset,
buf,
size,
(MR_BIT_IS_SET(DESC_OF(desc).oflags, MR_OFLAG_NONBLOCK)));
}
/**
* @brief This function write a device.
*
* @param desc The descriptor of the device.
* @param buf The buf buffer to be written.
* @param size The size of write.
*
* @return The size of the actual write, otherwise an error code.
*/
ssize_t mr_dev_write(int desc, const void *buf, size_t size)
{
MR_ASSERT((buf != MR_NULL) || (size == 0));
MR_DESC_CHECK(desc);
#ifdef MR_USING_RDWR_CTL
if (MR_BIT_IS_SET(DESC_OF(desc).oflags, MR_OFLAG_WRONLY) == MR_DISABLE)
{
return MR_ENOTSUP;
}
#endif /* MR_USING_RDWR_CTL */
/* Write buffer to the device */
return dev_write(DESC_OF(desc).dev,
DESC_OF(desc).offset,
buf,
size,
(MR_BIT_IS_SET(DESC_OF(desc).oflags, MR_OFLAG_NONBLOCK)));
}
/**
* @brief This function ioctl a device.
*
* @param desc The descriptor of the device.
* @param cmd The command of the device.
* @param args The arguments of the device.
*
* @return The arguments of the device, otherwise an error code.
*/
int mr_dev_ioctl(int desc, int cmd, void *args)
{
MR_DESC_CHECK(desc);
switch (cmd)
{
case MR_CTL_SET_OFFSET:
{
if (args != MR_NULL)
{
int offset = *(int *)args;
DESC_OF(desc).offset = offset;
return sizeof(offset);
}
return MR_EINVAL;
}
case MR_CTL_GET_OFFSET:
{
if (args != MR_NULL)
{
int *offset = (int *)args;
*offset = DESC_OF(desc).offset;
return sizeof(offset);
}
return MR_EINVAL;
}
case MR_CTL_GET_OFLAGS:
{
if (args != MR_NULL)
{
int *oflags = (int *)args;
*oflags = DESC_OF(desc).oflags;
}
return MR_EINVAL;
}
default:
{
/* I/O control to the device */
return dev_ioctl(DESC_OF(desc).dev, desc, DESC_OF(desc).offset, cmd, args);
}
}
}
/**
* @brief This function check if the descriptor is valid.
*
* @param desc The descriptor of the device.
*
* @return MR_TRUE if the descriptor is valid, otherwise MR_FALSE.
*/
int mr_dev_is_valid(int desc)
{
return DESC_IS_VALID(desc);
}
#ifdef MR_USING_MSH
#include "include/components/mr_msh.h"
static void msh_list_tree(struct mr_dev *parent, int level)
{
if (level == 0)
{
mr_msh_printf("|-- %-*s", MR_CFG_DEV_NAME_MAX, parent->name);
} else
{
mr_msh_printf("%*s|-- %-*s", level, " ", MR_CFG_DEV_NAME_MAX, parent->name);
}
for (size_t i = 0; i < MR_CFG_DESC_MAX; i++)
{
if (desc_map[i].dev == parent)
{
mr_msh_printf(" [%d]", i);
}
}
mr_msh_printf("\r\n");
for (struct mr_list *child = parent->clist.next; child != &parent->clist; child = child->next)
{
struct mr_dev *dev = MR_CONTAINER_OF(child, struct mr_dev, list);
msh_list_tree(dev, level + 4);
}
}
static int msh_cmd_dlist(int argc, void *argv)
{
msh_list_tree(&root_dev, 0);
return MR_EOK;
}
/**
* @brief Exports device MSH commands.
*/
MR_MSH_CMD_EXPORT(dlist, msh_cmd_dlist, "list all devices.");
#endif /* MR_USING_MSH */