Files
mr-library/source/device.c

804 lines
23 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"
static struct mr_dev root_dev = {.name = "dev", .type = MR_DEV_TYPE_ROOT, .parent = MR_NULL, .list = MR_LIST_INIT(
root_dev.list), .clist = MR_LIST_INIT(root_dev.clist)}; /**< Root device */
#ifndef MR_CFG_DESC_NUM
#define MR_CFG_DESC_NUM (32)
#endif /* MR_CFG_DESC_NUM */
static struct mr_dev_desc desc_map[MR_CFG_DESC_NUM] = {0}; /**< Device descriptor map */
#define DESC_OF(desc) (desc_map[(desc)]) /**< Descriptor of the device */
/* Check if the descriptor is valid */
#define DESC_IS_VALID(desc) (((desc) >= 0 && (desc) < MR_CFG_DESC_NUM) && ((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 */
MR_INLINE int dev_is_root(struct mr_dev *dev)
{
return dev->type == MR_DEV_TYPE_ROOT;
}
MR_INLINE struct mr_dev *dev_find_child(struct mr_dev *parent, const char *name)
{
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_LEN) == 0) {
return dev;
}
}
return MR_NULL;
}
MR_INLINE int dev_register_child(struct mr_dev *parent, struct mr_dev *child, const char *name)
{
if (dev_find_child(parent, name) != MR_NULL) {
return MR_EEXIST;
}
child->magic = MR_MAGIC_NUMBER;
strncpy(child->name, name, MR_CFG_DEV_NAME_LEN);
child->parent = parent;
mr_list_insert_before(&parent->clist, &child->list);
return MR_EOK;
}
static int dev_register_by_path(struct mr_dev *parent, struct mr_dev *dev, const char *path)
{
if (path[0] == '/') {
path++;
}
/* Check for child path separator */
const char *child_path = strchr(path, '/');
if (child_path != MR_NULL) {
char child_name[MR_CFG_DEV_NAME_LEN + 1] = {0};
size_t len = MR_BOUND(child_path - path, 0, MR_CFG_DEV_NAME_LEN);
/* Find the child device */
strncpy(child_name, path, len);
child_name[len] = '\0';
struct mr_dev *child = dev_find_child(parent, child_name);
if (child == MR_NULL) {
return MR_ENOTFOUND;
}
/* Register recursively */
return dev_register_by_path(child, dev, child_path);
} else {
/* Register with parent */
return dev_register_child(parent, dev, path);
}
}
static struct mr_dev *dev_find_by_path(struct mr_dev *parent, const char *path)
{
if (path[0] == '/') {
path++;
}
/* Check for child path separator */
const char *child_path = strchr(path, '/');
if (child_path != MR_NULL) {
char child_name[MR_CFG_DEV_NAME_LEN + 1] = {0};
size_t len = MR_BOUND(child_path - path, 0, MR_CFG_DEV_NAME_LEN);
/* Find the child device */
strncpy(child_name, path, len);
child_name[len] = '\0';
struct mr_dev *child = dev_find_child(parent, child_name);
if (child == MR_NULL) {
return MR_NULL;
}
/* Find recursively */
return dev_find_by_path(child, child_path);
} else {
/* Find with parent */
return dev_find_child(parent, path);
}
}
#ifdef MR_USING_RDWR_CTL
static int dev_lock_take(struct mr_dev *dev, uint32_t take, uint32_t 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;
}
}
if (dev->lock & take) {
return MR_EBUSY;
}
MR_BIT_SET(dev->lock, set);
return MR_EOK;
}
static void dev_lock_release(struct mr_dev *dev, uint32_t release)
{
/* Continue iterating until reach the root device */
if (dev_is_root(dev->parent) != MR_TRUE) {
dev_lock_release(dev->parent, release);
}
MR_BIT_CLR(dev->lock, release);
}
#endif /* MR_USING_RDWR_CTL */
MR_INLINE struct mr_dev *dev_find(const char *path)
{
/* Check whether the path is absolute */
if (*path == '/') {
path++;
const char *next_slash = strchr(path, '/');
if ((next_slash == MR_NULL) ||
(strncmp(path, root_dev.name, MR_BOUND(next_slash - path, 0, MR_CFG_DEV_NAME_LEN)) !=
0)) {
return MR_NULL;
}
path += MR_BOUND(next_slash - path, 0, MR_CFG_DEV_NAME_LEN);
}
/* Find the device from the root device */
return dev_find_by_path(&root_dev, path);
}
MR_INLINE int dev_register(struct mr_dev *dev, const char *path)
{
/* Check whether the path is absolute */
if (*path == '/') {
path++;
const char *next_slash = strchr(path, '/');
if ((next_slash == MR_NULL) ||
(strncmp(path, root_dev.name, MR_BOUND(next_slash - path, 0, MR_CFG_DEV_NAME_LEN)) !=
0)) {
return MR_EINVAL;
}
path += MR_BOUND(next_slash - path, 0, MR_CFG_DEV_NAME_LEN);
}
/* Register the device with the root device */
mr_interrupt_disable();
int ret = dev_register_by_path(&root_dev, dev, path);
mr_interrupt_enable();
return ret;
}
static int dev_open(struct mr_dev *dev, int flags)
{
#ifdef MR_USING_RDWR_CTL
if (MR_BIT_IS_SET(dev->flags, flags) != 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, flags);
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;
}
}
}
/* Increase the reference count */
dev->ref_count++;
return MR_EOK;
}
static 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 position, int sync, void *buf, size_t count)
{
#ifdef MR_USING_RDWR_CTL
do {
mr_interrupt_disable();
int ret = dev_lock_take(dev, (MR_LOCK_RD | MR_LOCK_SLEEP), MR_LOCK_RD);
if (ret < 0) {
mr_interrupt_enable();
return ret;
}
mr_interrupt_enable();
} while (0);
#endif /* MR_USING_RDWR_CTL */
/* Update information */
dev->sync = sync;
dev->position = position;
/* Read buffer from the device */
ssize_t ret = dev->ops->read(dev, buf, count);
#ifdef MR_USING_RDWR_CTL
dev_lock_release(dev, MR_LOCK_RD);
#endif /* MR_USING_RDWR_CTL */
return ret;
}
MR_INLINE ssize_t dev_write(struct mr_dev *dev,
int position,
int sync,
const void *buf,
size_t count)
{
#ifdef MR_USING_RDWR_CTL
do {
mr_interrupt_disable();
int ret = dev_lock_take(dev,
(MR_LOCK_WR |
MR_LOCK_SLEEP |
(sync == MR_SYNC ? MR_LOCK_NONBLOCK : 0)),
MR_LOCK_WR);
if (ret < 0) {
mr_interrupt_enable();
return ret;
}
mr_interrupt_enable();
} while (0);
#endif /* MR_USING_RDWR_CTL */
/* Update information */
dev->sync = sync;
dev->position = position;
/* Write buffer to the device */
ssize_t ret = dev->ops->write(dev, buf, count);
#ifdef MR_USING_RDWR_CTL
dev_lock_release(dev, MR_LOCK_WR);
if ((sync == MR_ASYNC) && (ret > 0)) {
mr_interrupt_disable();
dev_lock_take(dev, 0, MR_LOCK_NONBLOCK);
mr_interrupt_enable();
}
#endif /* MR_USING_RDWR_CTL */
return ret;
}
MR_INLINE int dev_ioctl(struct mr_dev *dev, int position, int sync, int cmd, void *args)
{
if (dev->ops->ioctl == MR_NULL) {
return MR_ENOTSUP;
}
#ifdef MR_USING_RDWR_CTL
do {
/* Lock only when user -> device command */
if (cmd) {
mr_interrupt_disable();
int ret = dev_lock_take(dev,
(MR_LOCK_RDWR | MR_LOCK_SLEEP | MR_LOCK_NONBLOCK),
MR_LOCK_RDWR);
if (ret < 0) {
mr_interrupt_enable();
return ret;
}
mr_interrupt_enable();
}
} while (0);
#endif /* MR_USING_RDWR_CTL */
/* Update information */
dev->sync = sync;
dev->position = position;
/* I/O control to the device */
int ret = dev->ops->ioctl(dev, cmd, args);
#ifdef MR_USING_RDWR_CTL
dev_lock_release(dev, MR_LOCK_RDWR);
#endif /* MR_USING_RDWR_CTL */
return ret;
}
MR_INLINE int desc_allocate(const char *path)
{
int desc = -1;
struct mr_dev *dev = dev_find(path);
if (dev == MR_NULL) {
return MR_ENOTFOUND;
}
/* Find a free descriptor */
for (size_t i = 0; i < MR_CFG_DESC_NUM; i++) {
if (DESC_OF(i).dev == MR_NULL) {
desc = (int)i;
break;
}
}
if (desc < 0) {
return MR_ENOMEM;
}
DESC_OF(desc).dev = dev;
DESC_OF(desc).flags = MR_O_CLOSED;
DESC_OF(desc).position = -1;
DESC_OF(desc).rd_call.fn = MR_NULL;
DESC_OF(desc).wr_call.fn = MR_NULL;
mr_list_init(&DESC_OF(desc).rd_call.list);
mr_list_init(&DESC_OF(desc).wr_call.list);
return desc;
}
MR_INLINE void desc_free(int desc)
{
if (DESC_IS_VALID(desc) == MR_TRUE) {
DESC_OF(desc).dev = MR_NULL;
DESC_OF(desc).flags = MR_O_CLOSED;
DESC_OF(desc).position = -1;
DESC_OF(desc).rd_call.fn = MR_NULL;
DESC_OF(desc).wr_call.fn = MR_NULL;
mr_list_remove(&DESC_OF(desc).rd_call.list);
mr_list_remove(&DESC_OF(desc).wr_call.list);
}
}
/**
* @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 flags The support flags of the device.
* @param ops The operations of the device.
* @param drv The driver of the device.
*
* @return 0 on success, otherwise an error code.
*
* @retval -3 a node in the path is not found.
* @retval -5 name is already exists.
* @retval -7 path is invalid.
*/
int mr_dev_register(struct mr_dev *dev,
const char *path,
int type,
int flags,
struct mr_dev_ops *ops,
struct mr_drv *drv)
{
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(flags >= MR_O_CLOSED);
MR_ASSERT(ops != MR_NULL);
MR_ASSERT((ops->read != MR_NULL) || (MR_BIT_IS_SET(flags, MR_O_RDONLY) == MR_DISABLE));
MR_ASSERT((ops->write != MR_NULL) || (MR_BIT_IS_SET(flags, MR_O_WRONLY) == MR_DISABLE));
/* Initialize the fields */
dev->magic = 0;
memset(dev->name, '\0', MR_CFG_DEV_NAME_LEN);
dev->type = type;
dev->flags = flags;
dev->parent = MR_NULL;
mr_list_init(&dev->list);
mr_list_init(&dev->clist);
dev->ref_count = 0;
#ifdef MR_USING_RDWR_CTL
dev->lock = 0;
#endif /* MR_USING_RDWR_CTL */
dev->sync = MR_SYNC;
dev->position = -1;
mr_list_init(&dev->rd_call_list);
mr_list_init(&dev->wr_call_list);
dev->ops = 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.
*
* @return 0 on success, otherwise an error code.
*
* @retval -6 device is closed or not supported.
* @retval other error code.
*/
int mr_dev_isr(struct mr_dev *dev, int event, void *args)
{
MR_ASSERT(dev != MR_NULL);
MR_ASSERT(event >= 0);
if (dev->ref_count == 0) {
return MR_ENOTSUP;
}
if (dev->ops->isr != MR_NULL) {
ssize_t ret = dev->ops->isr(dev, event, args);
if (ret < 0) {
return (int)ret;
}
}
/* Call the all set callbacks */
switch (event & MR_ISR_MASK) {
case MR_ISR_RD: {
for (struct mr_list *list = dev->rd_call_list.next;
list != &dev->rd_call_list;
list = list->next) {
struct mr_dev_desc *desc = (struct mr_dev_desc *)MR_CONTAINER_OF(list,
struct mr_dev_desc,
rd_call.list);
if (desc->rd_call.fn != MR_NULL) {
desc->rd_call.fn((int)(desc - &desc_map[0]), args);
}
}
return MR_EOK;
}
case MR_ISR_WR: {
#ifdef MR_USING_RDWR_CTL
dev_lock_release(dev, MR_LOCK_NONBLOCK);
#endif /* MR_USING_RDWR_CTL */
for (struct mr_list *list = dev->wr_call_list.next;
list != &dev->wr_call_list;
list = list->next) {
struct mr_dev_desc *desc = (struct mr_dev_desc *)MR_CONTAINER_OF(list,
struct mr_dev_desc,
wr_call.list);
if (desc->wr_call.fn != MR_NULL) {
desc->wr_call.fn((int)(desc - &desc_map[0]), args);
}
}
return MR_EOK;
}
default: {
return MR_ENOTSUP;
}
}
}
/**
* @brief This function open a device.
*
* @param path The path of the device.
* @param flags The open flags.
*
* @return The descriptor of the device on success, the support flags of the device if flags is a query,
* otherwise an error code.
*
* @retval -1 not enough descriptor or memory.
* @retval -3 the path is not found.
* @retval -6 not supported flags.
* @retval other error code.
*/
int mr_dev_open(const char *path, int flags)
{
MR_ASSERT(path != MR_NULL);
MR_ASSERT(flags >= MR_O_CLOSED);
/* Query device flags */
if (flags == MR_O_QUERY) {
struct mr_dev *dev = dev_find(path);
if (dev == MR_NULL) {
return MR_ENOTFOUND;
}
return dev->flags;
}
/* Allocate descriptor and open device */
int desc = desc_allocate(path);
if (desc < 0) {
return desc;
}
int ret = dev_open(DESC_OF(desc).dev, flags);
if (ret < 0) {
desc_free(desc);
return ret;
}
/* Set descriptor flags */
DESC_OF(desc).flags = flags;
return desc;
}
/**
* @brief This function close a device.
*
* @param desc The descriptor of the device.
*
* @return 0 on success, otherwise an error code.
*
* @retval -7 descriptor is invalid.
* @retval other error code.
*/
int mr_dev_close(int desc)
{
MR_DESC_CHECK(desc);
/* Close the device and free the descriptor */
int ret = dev_close(DESC_OF(desc).dev);
if (ret < 0) {
return ret;
}
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 count The count of read.
*
* @return The size of the actual read, otherwise an error code.
*
* @retval -4 is currently accessed by another.
* @retval -6 not supported read.
* @retval -7 descriptor is invalid.
* @retval other error code.
*/
ssize_t mr_dev_read(int desc, void *buf, size_t count)
{
MR_ASSERT((buf != MR_NULL) || (count == 0));
MR_DESC_CHECK(desc);
#ifdef MR_USING_RDWR_CTL
if (MR_BIT_IS_SET(DESC_OF(desc).flags, MR_O_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).position,
MR_BIT_IS_SET(DESC_OF(desc).flags, MR_O_NONBLOCK),
buf,
count);
}
/**
* @brief This function write a device.
*
* @param desc The descriptor of the device.
* @param buf The buf buffer to be written.
* @param count The count of write.
*
* @return The size of the actual write, otherwise an error code.
*
* @retval -4 is currently accessed by another.
* @retval -6 not supported write.
* @retval -7 descriptor is invalid.
* @retval other error code.
*/
ssize_t mr_dev_write(int desc, const void *buf, size_t count)
{
MR_ASSERT((buf != MR_NULL) || (count == 0));
MR_DESC_CHECK(desc);
#ifdef MR_USING_RDWR_CTL
if (MR_BIT_IS_SET(DESC_OF(desc).flags, MR_O_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).position,
MR_BIT_IS_SET(DESC_OF(desc).flags, MR_O_NONBLOCK),
buf,
count);
}
/**
* @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 size of the actual control, otherwise an error code.
*
* @retval -4 is currently accessed by another.
* @retval -6 not supported ioctl or command.
* @retval -7 descriptor or argument is invalid.
* @retval other error code.
*/
int mr_dev_ioctl(int desc, int cmd, void *args)
{
MR_DESC_CHECK(desc);
switch (cmd) {
case MR_IOC_SPOS: {
if (args != MR_NULL) {
int position = *(int *)args;
DESC_OF(desc).position = position;
return sizeof(position);
}
return MR_EINVAL;
}
case MR_IOC_SRCB: {
#ifdef MR_USING_RDWR_CTL
if (MR_BIT_IS_SET(DESC_OF(desc).flags, MR_O_RDONLY) == MR_DISABLE) {
return MR_ENOTSUP;
}
#endif /* MR_USING_RDWR_CTL */
void (*fn)(int desc, void *args) = (void (*)(int desc, void *args))args;
/* Link or unlink the callback function */
DESC_OF(desc).rd_call.fn = fn;
mr_interrupt_disable();
if (fn != MR_NULL) {
mr_list_insert_before(&DESC_OF(desc).dev->rd_call_list,
&DESC_OF(desc).rd_call.list);
} else {
mr_list_remove(&DESC_OF(desc).rd_call.list);
}
mr_interrupt_enable();
return sizeof(fn);
}
case MR_IOC_SWCB: {
#ifdef MR_USING_RDWR_CTL
if (MR_BIT_IS_SET(DESC_OF(desc).flags, MR_O_WRONLY) == MR_DISABLE) {
return MR_ENOTSUP;
}
#endif /* MR_USING_RDWR_CTL */
void (*fn)(int desc, void *args) = (void (*)(int desc, void *args))args;
/* Link or unlink the callback function */
DESC_OF(desc).wr_call.fn = fn;
mr_interrupt_disable();
if (fn != MR_NULL) {
mr_list_insert_before(&DESC_OF(desc).dev->wr_call_list,
&DESC_OF(desc).wr_call.list);
} else {
mr_list_remove(&DESC_OF(desc).wr_call.list);
}
mr_interrupt_enable();
return sizeof(fn);
}
case MR_IOC_GPOS: {
if (args != MR_NULL) {
int *position = (int *)args;
*position = DESC_OF(desc).position;
return sizeof(position);
}
return MR_EINVAL;
}
case MR_IOC_GRCB: {
#ifdef MR_USING_RDWR_CTL
if (MR_BIT_IS_SET(DESC_OF(desc).flags, MR_O_RDONLY) == MR_DISABLE) {
return MR_ENOTSUP;
}
#endif /* MR_USING_RDWR_CTL */
if (args != MR_NULL) {
*(void (**)(int desc, void *args))args = DESC_OF(desc).rd_call.fn;
return sizeof(DESC_OF(desc).rd_call.fn);
}
return MR_EINVAL;
}
case MR_IOC_GWCB: {
#ifdef MR_USING_RDWR_CTL
if (MR_BIT_IS_SET(DESC_OF(desc).flags, MR_O_WRONLY) == MR_DISABLE) {
return MR_ENOTSUP;
}
#endif /* MR_USING_RDWR_CTL */
if (args != MR_NULL) {
*(void (**)(int desc, void *args))args = DESC_OF(desc).wr_call.fn;
return sizeof(DESC_OF(desc).wr_call.fn);
}
return MR_EINVAL;
}
default: {
/* I/O control to the device */
return dev_ioctl(DESC_OF(desc).dev,
DESC_OF(desc).position,
MR_BIT_IS_SET(DESC_OF(desc).flags, MR_O_NONBLOCK),
cmd,
args);
}
}
}
#if defined(MR_USING_MSH) && defined(MR_USING_MSH_DEV_CMD)
#include "include/components/mr_msh.h"
static int dev_get_path(struct mr_dev *dev, char *buf, size_t bufsz)
{
int ret = 0;
/* Continue iterating until reach the null parent */
if (dev->parent != MR_NULL) {
ret = dev_get_path(dev->parent, buf, bufsz);
}
/* 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;
}
int msh_dev_get_path(int desc, char *buf, size_t size)
{
MR_DESC_CHECK(desc);
return dev_get_path(DESC_OF(desc).dev, buf, size);
}
void msh_dlist_tree(struct mr_dev *parent, int level)
{
if (level == 0) {
mr_msh_printf("|%s %-*s", mr_strflags(parent->flags), MR_CFG_DEV_NAME_LEN, parent->name);
} else {
mr_msh_printf("%*s|%s %-*s",
level,
" ",
mr_strflags(parent->flags),
MR_CFG_DEV_NAME_LEN,
parent->name);
}
for (size_t i = 0; i < MR_CFG_DESC_NUM; 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_dlist_tree(dev, level + 7);
}
}
struct mr_dev *msh_get_root(void)
{
return &root_dev;
}
#endif /* defined(MR_USING_MSH) && defined(MR_USING_MSH_DEV_CMD) */