Files
mr-library/source/device.c
2024-04-23 22:56:51 +08:00

1024 lines
24 KiB
C

/*
* @copyright (c) 2023-2024, MR Development Team
*
* @license SPDX-License-Identifier: Apache-2.0
*
* @date 2023-10-20 MacRsh First version
* @date 2024-04-07 MacRsh Reconstructed
*/
#include "../mr-library/include/mr_api.h"
#include <string.h>
#undef MR_LOG_TAG
#define MR_LOG_TAG "device"
#define _MAGIC_NUMBER (0xdeadbeef) /**< Magic number */
#define _LOCK_RD_MASK (0xffff0000) /**< Read lock mask */
#define _LOCK_WR_MASK (0x0000ffff) /**< Write lock mask */
#define _LOCK_ALL_MASK (0xffffffff) /**< Read/write lock mask */
/**
* @brief Device event complete structure.
*/
struct _device_event
{
struct mr_list list; /**< Event list */
int descriptor; /**< Event descriptor */
uint32_t event; /**< Event */
void (*callback)(int descriptor, uint32_t event,
void *args); /**< Callback function */
};
static struct mr_device _root_device = {
.magic = _MAGIC_NUMBER,
.name = "/",
.list = MR_LIST_INIT(&_root_device.list),
.clist = MR_LIST_INIT(&_root_device.clist),
.parent = NULL};
#ifndef MR_CFG_DESCRIPTOR_MAX
#define MR_CFG_DESCRIPTOR_MAX (128)
#endif /* MR_CFG_DESCRIPTOR_MAX */
static struct mr_descriptor _descriptor_map[MR_CFG_DESCRIPTOR_MAX] = {0};
MR_INLINE struct mr_device *_device_find_from(const char *name,
struct mr_device *parent)
{
/* Deal with special names */
if (strcmp(name, "..") == 0)
{
return parent->parent;
} else if (strcmp(name, ".") == 0)
{
return parent;
}
/* Find the device with the specified name */
for (struct mr_list *list = parent->clist.next; list != &parent->clist;
list = list->next)
{
struct mr_device *iter = MR_CONTAINER_OF(list, struct mr_device, list);
if (strcmp(name, iter->name) == 0)
{
return iter;
}
}
return NULL;
}
MR_INLINE struct mr_device *_device_next_find(const char **path,
struct mr_device *parent)
{
/* Skip the leading '/' */
if ((*path)[0] == '/')
{
(*path)++;
}
/* Find the next path */
const char *next_path = strchr((*path), '/');
if (next_path != NULL)
{
char name[sizeof(parent->name) + 1];
size_t len = MR_BOUND(next_path - (*path), 0, sizeof(name));
strncpy(name, *path, len);
name[len] = '\0';
*path = next_path + 1;
/* Find the next device */
return _device_find_from(name, parent);
} else
{
/* Without next */
return NULL;
}
}
static int _device_register_iter(struct mr_device *device, const char *path,
struct mr_device *parent)
{
struct mr_device *next_parent = _device_next_find(&path, parent);
if (next_parent != NULL)
{
return _device_register_iter(device, path, next_parent);
} else
{
/* Check if the device exists */
if (_device_find_from(path, parent) != NULL)
{
return MR_EEXIST;
}
/* Register the device */
device->magic = _MAGIC_NUMBER;
strncpy(device->name, path, sizeof(device->name));
mr_list_insert_before(&parent->clist, &device->list);
device->parent = parent;
return MR_EOK;
}
}
static struct mr_device *_device_find_iter(const char *path,
struct mr_device *parent)
{
struct mr_device *next_parent = _device_next_find(&path, parent);
if (next_parent != NULL)
{
return _device_find_iter(path, next_parent);
} else
{
return _device_find_from(path, parent);
}
}
MR_INLINE struct mr_device *_device_find(const char *path)
{
struct mr_device *device;
/* Critical section enter */
mr_critical_enter();
/* Find the device */
device = _device_find_iter(path, &_root_device);
/* Critical section exit */
mr_critical_exit();
return device;
}
MR_INLINE bool _device_flags_is_valid(struct mr_device *device, uint32_t flags)
{
return MR_BIT_IS_SET(device->flags, flags);
}
MR_INLINE bool _descriptor_is_valid(int descriptor)
{
return (descriptor >= 0) && (descriptor < MR_ARRAY_NUM(_descriptor_map)) &&
(_descriptor_map[descriptor].device != NULL);
}
MR_INLINE bool _descriptor_flags_is_valid(int descriptor, uint32_t flags)
{
return MR_BIT_IS_SET(_descriptor_map[descriptor].flags, flags);
}
MR_INLINE int _device_take(struct mr_device *device, int descriptor,
uint32_t mask)
{
uint32_t lock;
int ret;
/* If the device is not FDX, the read/write must be locked */
mask = (device->fdx == true) ? mask : _LOCK_ALL_MASK;
/* Calculate the lock mask, since the descriptor can be 0, need to add 1 */
lock = (((descriptor + 1) << 16) | (descriptor + 1)) & mask;
/* Critical section enter */
mr_critical_enter();
if (_descriptor_is_valid(descriptor) == false)
{
ret = MR_EINVAL;
} else if ((device->lock & mask) == 0)
{
MR_BIT_SET(device->lock, lock);
ret = MR_EOK;
} else
{
ret = MR_EBUSY;
}
/* Critical section exit */
mr_critical_exit();
return ret;
}
MR_INLINE void _device_release(struct mr_device *device, uint32_t mask)
{
/* If the device is not FDX, the read/write must be locked */
mask = (device->fdx == true) ? mask : _LOCK_ALL_MASK;
/* Release the device lock */
MR_BIT_CLR(device->lock, mask);
}
MR_INLINE int _device_event_create(struct mr_device *device, int descriptor,
struct mr_device_event *event)
{
struct _device_event *_event;
int ret;
/* Critical section enter */
mr_critical_enter();
/* Check if the event exists */
for (struct mr_list *list = device->event_list.next;
list != &device->event_list; list = list->next)
{
_event = MR_CONTAINER_OF(list, struct _device_event, list);
if ((_event->descriptor == descriptor) &&
(_event->event == event->event))
{
ret = MR_EEXIST;
goto _exit;
}
}
/* Create the new event */
_event = mr_malloc(sizeof(struct _device_event));
if (_event == NULL)
{
ret = MR_ENOMEM;
goto _exit;
}
/* Add the event */
mr_list_init(&_event->list);
_event->descriptor = descriptor;
_event->event = event->event;
_event->callback = event->callback;
mr_list_insert_before(&device->event_list, &_event->list);
ret = MR_EOK;
_exit:
/* Critical section exit */
mr_critical_exit();
return ret;
}
MR_INLINE int _device_event_destroy(struct mr_device *device, int descriptor,
struct mr_device_event *event)
{
int ret;
/* Critical section enter */
mr_critical_enter();
/* Find the event */
for (struct mr_list *list = device->event_list.next;
list != &device->event_list; list = list->next)
{
struct _device_event *_event =
MR_CONTAINER_OF(list, struct _device_event, list);
if ((_event->descriptor == descriptor) &&
(_event->event == event->event))
{
/* Destroy the event */
mr_list_remove(&_event->list);
mr_free(_event);
ret = MR_EOK;
goto _exit;
}
}
/* Not found the event */
ret = MR_ENOENT;
_exit:
/* Critical section exit */
mr_critical_exit();
return ret;
}
MR_INLINE void _device_event_destroy_all(struct mr_device *device,
int descriptor)
{
/* Critical section enter */
mr_critical_enter();
/* Destroy all events for the specified descriptor */
for (struct mr_list *list = device->event_list.next;
list != &device->event_list; list = list->next)
{
struct _device_event *_event =
MR_CONTAINER_OF(list, struct _device_event, list);
if (_event->descriptor == descriptor)
{
/* Destroy the event */
list = list->prev;
mr_list_remove(&_event->list);
mr_free(_event);
}
}
/* Critical section exit */
mr_critical_exit();
}
MR_INLINE void _device_event_handler(struct mr_device *device, uint32_t event,
void *args)
{
for (struct mr_list *list = device->event_list.next;
list != &device->event_list; list = list->next)
{
struct _device_event *_event =
MR_CONTAINER_OF(list, struct _device_event, list);
/* Call the callback if the event matches */
if (_event->event == event)
{
_event->callback(_event->descriptor, _event->event, args);
}
}
}
MR_INLINE int _descriptor_allocate(struct mr_device *device, uint32_t flags)
{
int descriptor = -1;
/* Check if the flags are valid */
if (_device_flags_is_valid(device, flags) == false)
{
return MR_EPERM;
}
/* Critical section enter */
mr_critical_enter();
/* Try to allocate the descriptor */
for (int i = 0; i < MR_ARRAY_NUM(_descriptor_map); i++)
{
if (_descriptor_map[i].device == NULL)
{
_descriptor_map[i].device = device;
_descriptor_map[i].flags = flags;
_descriptor_map[i].pos = -1;
descriptor = i;
break;
}
}
/* Critical section exit */
mr_critical_exit();
/* Return the descriptor or error */
return (descriptor >= 0) ? descriptor : MR_ENOMEM;
}
MR_INLINE void _descriptor_free(int descriptor)
{
/* Critical section enter */
mr_critical_enter();
/* Free the descriptor */
_descriptor_map[descriptor].device = NULL;
/* Critical section exit */
mr_critical_exit();
}
static int _device_register(struct mr_device *device, const char *path)
{
int ret;
/* Critical section enter */
mr_critical_enter();
ret = _device_register_iter(device, path, &_root_device);
/* Critical section exit */
mr_critical_exit();
return ret;
}
static int _device_unregister(struct mr_device *device)
{
int ret;
/* Critical section enter */
mr_critical_enter();
/* Unregister the device only when there are no more references to it */
if (device->ref_count == 0)
{
device->magic = 0;
mr_list_remove(&device->list);
device->parent = NULL;
ret = MR_EOK;
} else
{
ret = MR_EBUSY;
}
/* Critical section exit */
mr_critical_exit();
return ret;
}
static int _device_isr(struct mr_device *device, uint32_t event, void *args)
{
uint32_t mask;
int ret;
/* Critical section enter */
mr_critical_enter();
/* If the device has no references, cannot handle the ISR */
if (device->ref_count == 0)
{
ret = MR_EPERM;
goto _exit;
}
/* Call the device ISR */
if (device->ops->isr != NULL)
{
ret = device->ops->isr(device, event, args);
if (ret < 0)
{
goto _exit;
}
}
/* Release the device based on event */
mask = (event & MR_EVENT_RD_COMPLETE) ? _LOCK_RD_MASK : 0;
mask |= (event & MR_EVENT_WR_COMPLETE) ? _LOCK_WR_MASK : 0;
_device_release(device, mask);
/* Call the event handler */
_device_event_handler(device, event, args);
ret = MR_EOK;
_exit:
/* Critical section exit */
mr_critical_exit();
return ret;
}
static int _device_open(const char *path, uint32_t flags)
{
struct mr_device *device;
int descriptor;
int ret;
/* Find the device */
device = _device_find(path);
if (device == NULL)
{
return MR_ENOENT;
}
/* Allocate a descriptor */
descriptor = _descriptor_allocate(device, flags);
if (descriptor < 0)
{
return descriptor;
}
/* Take the device */
ret = _device_take(device, descriptor, _LOCK_ALL_MASK);
if (ret < 0)
{
_descriptor_free(descriptor);
return ret;
}
/* Device will only be opened the first time it is accessed */
if ((device->ref_count == 0) && (device->ops->open != NULL))
{
ret = device->ops->open(device);
if (ret < 0)
{
_descriptor_free(descriptor);
goto _exit;
}
}
/* Increment the reference count */
device->ref_count++;
/* Return the descriptor */
ret = descriptor;
_exit:
/* Release the device */
_device_release(device, _LOCK_ALL_MASK);
return ret;
}
static int _device_close(int descriptor)
{
struct mr_device *device;
int ret;
/* Find the device */
device = _descriptor_map[descriptor].device;
if (device == NULL)
{
return MR_EINVAL;
}
/* Take the device */
ret = _device_take(device, descriptor, _LOCK_ALL_MASK);
if (ret < 0)
{
return ret;
}
/* Device will only be closed the last time it is accessed */
if ((device->ref_count == 1) && (device->ops->close != NULL))
{
ret = device->ops->close(device);
if (ret < 0)
{
goto _exit;
}
}
/* Decrement the reference count */
device->ref_count--;
/* Destroy events associated with the descriptor */
_device_event_destroy_all(device, descriptor);
/* Free the descriptor */
_descriptor_free(descriptor);
_exit:
/* Release the device */
_device_release(device, _LOCK_ALL_MASK);
return ret;
}
static ssize_t _device_read(int descriptor, void *buf, size_t count)
{
struct mr_device *device;
ssize_t ret;
int pos;
/* Find the device */
device = _descriptor_map[descriptor].device;
if (device == NULL)
{
return MR_EINVAL;
}
/* Take the device */
ret = _device_take(device, descriptor, _LOCK_RD_MASK);
if (ret < 0)
{
return ret;
}
/* Get the position */
pos = _descriptor_map[descriptor].pos;
/* Async or sync read */
if (_descriptor_flags_is_valid(descriptor, MR_FLAG_RDONLY_ASYNC) == true)
{
/* Async read */
ret = device->ops->read_async(device, pos, buf, count);
if (ret > 0)
{
/* If the operation is successful, the device will not be released
* until the operation is finished */
return ret;
}
} else if (_descriptor_flags_is_valid(descriptor, MR_FLAG_RDONLY) == true)
{
/* Sync read */
ret = device->ops->read(device, pos, buf, count);
} else
{
ret = MR_EACCES;
}
/* Release the device */
_device_release(device, _LOCK_RD_MASK);
return ret;
}
static ssize_t _device_write(int descriptor, const void *buf, size_t count)
{
struct mr_device *device;
ssize_t ret;
int pos;
/* Find the device */
device = _descriptor_map[descriptor].device;
if (device == NULL)
{
return MR_EINVAL;
}
/* Take the device */
ret = _device_take(device, descriptor, _LOCK_WR_MASK);
if (ret < 0)
{
return ret;
}
/* Get the position */
pos = _descriptor_map[descriptor].pos;
/* Async or sync write */
if (_descriptor_flags_is_valid(descriptor, MR_FLAG_WRONLY_ASYNC) == true)
{
/* Async write */
ret = device->ops->write_async(device, pos, buf, count);
if (ret > 0)
{
/* If the operation is successful, the device will not be released
* until the operation is finished */
return ret;
}
} else if (_descriptor_flags_is_valid(descriptor, MR_FLAG_WRONLY) == true)
{
/* Sync write */
ret = device->ops->write(device, pos, buf, count);
} else
{
ret = MR_EACCES;
}
/* Release the device */
_device_release(device, _LOCK_WR_MASK);
return ret;
}
static int _device_ioctl(int descriptor, int cmd, void *args)
{
struct mr_device *device;
int pos, ret;
/* Find the device */
device = _descriptor_map[descriptor].device;
if (device == NULL)
{
return MR_EINVAL;
}
/* Take the device */
ret = _device_take(device, descriptor, _LOCK_ALL_MASK);
if (ret < 0)
{
return ret;
}
/* Get the position */
pos = _descriptor_map[descriptor].pos;
/* Control the device */
switch (cmd)
{
case MR_CTRL_SET(MR_CMD_POS):
{
/* Check the argument */
if (args == NULL)
{
ret = MR_EINVAL;
goto _exit;
}
/* Set the position */
_descriptor_map[descriptor].pos = *(int *)args;
ret = MR_EOK;
break;
}
case MR_CTRL_GET(MR_CMD_POS):
{
/* Check the argument */
if (args == NULL)
{
ret = MR_EINVAL;
goto _exit;
}
/* Get the position */
*(int *)args = pos;
ret = MR_EOK;
break;
}
case MR_CTRL_NEW(MR_CMD_EVENT):
{
/* Check the argument */
if (args == NULL)
{
ret = MR_EINVAL;
goto _exit;
}
/* Create the event */
ret = _device_event_create(device, descriptor,args);
break;
}
case MR_CTRL_DEL(MR_CMD_EVENT):
{
/* Check the argument */
if (args == NULL)
{
ret = MR_EINVAL;
goto _exit;
}
/* Destroy the event */
ret = _device_event_destroy(device, descriptor, args);
break;
}
default:
{
/* CMD is defined by the device */
ret = device->ops->ioctl(device, pos, cmd, args);
break;
}
}
_exit:
/* Release the device */
_device_release(device, _LOCK_ALL_MASK);
return ret;
}
/**
* @brief This function gets the descriptor map.
*
* @param descriptor_map The descriptor map.
*
* @return The count of the descriptor map.
*/
size_t _mr_descriptor_map_get(struct mr_descriptor **descriptor_map)
{
MR_ASSERT(descriptor_map != NULL);
*descriptor_map = _descriptor_map;
return sizeof(_descriptor_map) / sizeof(struct mr_descriptor);
}
/**
* @brief This function register a device.
*
* @param device The device.
* @param path The path of the device.
* @param type The type of the device.
* @param ops The operations of the device.
* @param driver The driver of the device.
*
* @return The error code.
*/
int mr_device_register(struct mr_device *device, const char *path,
uint32_t type, struct mr_device_ops *ops,
const void *driver)
{
MR_ASSERT((device != NULL) && (device->magic != _MAGIC_NUMBER));
MR_ASSERT(path != NULL);
static struct mr_device_ops null_ops = {NULL};
int ret;
/* Set default ops if not specified */
ops = (ops == NULL) ? &null_ops : ops;
/* Initialize the device */
mr_list_init(&device->list);
mr_list_init(&device->clist);
device->parent = NULL;
device->type = type & (~MR_DEVICE_TYPE_FDX);
device->fdx = MR_BIT_IS_SET(type, MR_DEVICE_TYPE_FDX);
device->flags = (ops->read != NULL ? MR_FLAG_RDONLY : 0) |
(ops->write != NULL ? MR_FLAG_WRONLY : 0) |
(ops->read_async != NULL ? MR_FLAG_RDONLY_ASYNC : 0) |
(ops->write_async != NULL ? MR_FLAG_WRONLY_ASYNC : 0);
device->ref_count = 0;
device->lock = 0;
device->ops = ops;
device->driver = driver;
mr_list_init(&device->event_list);
/* Register the device */
ret = _device_register(device, path);
if (ret < 0)
{
goto _exit;
}
/* Return error code */
return ret;
_exit:
MR_LOG_E("Register '%s' failed: %s.\r\n", path, mr_strerror(ret));
return ret;
}
/**
* @brief This function unregister a device.
*
* @param device The device.
*
* @return The error code.
*/
int mr_device_unregister(struct mr_device *device)
{
MR_ASSERT((device != NULL) && (device->magic == _MAGIC_NUMBER));
int ret;
/* Unregister the device */
ret = _device_unregister(device);
if (ret < 0)
{
goto _exit;
}
/* Return error code */
return ret;
_exit:
MR_LOG_E("Unregister '%s' failed: %s.\r\n", device->name, mr_strerror(ret));
return ret;
}
/**
* @brief This function ISR of a device.
*
* @param device The device.
* @param event The event of the ISR.
* @param args The arguments.
*
* @return The error code.
*/
int mr_device_isr(struct mr_device *device, uint32_t event, void *args)
{
MR_ASSERT((device != NULL) && (device->magic == _MAGIC_NUMBER));
/* Call the device ISR */
return _device_isr(device, event, args);
}
/**
* @brief This function open a device.
*
* @param device The device.
* @param path The path of the device.
* @param flags The flags of the device.
*
* @return The descriptor of the device on success, otherwise an error code.
*/
int mr_device_open(const char *path, uint32_t flags)
{
MR_ASSERT(path != NULL);
MR_ASSERT(flags != 0);
int ret;
/* Open the device */
ret = _device_open(path, flags);
if (ret < 0)
{
goto _exit;
}
/* Return the descriptor */
return ret;
_exit:
MR_LOG_E("Open '%s' failed: %s.\r\n", path, mr_strerror(ret));
return ret;
}
/**
* @brief This function close a device.
*
* @param descriptor The descriptor of the device.
*
* @return The error code.
*/
int mr_device_close(int descriptor)
{
int ret;
/* Check if the descriptor is valid */
if (_descriptor_is_valid(descriptor) == false)
{
return MR_EINVAL;
}
/* Close the device */
ret = _device_close(descriptor);
if (ret < 0)
{
goto _exit;
}
/* Return error code */
return ret;
_exit:
MR_LOG_E("Close '%d' failed: %s.\r\n", descriptor, mr_strerror(ret));
return ret;
}
/**
* @brief This function read data from a device.
*
* @param descriptor The descriptor of the device.
* @param buf The buffer to be read.
* @param count The count of read.
*
* @return The size of the actual read, otherwise an error code.
*
* @note If the async operation is successful, the device will not be released
* until the operation is complete.
*/
ssize_t mr_device_read(int descriptor, void *buf, size_t count)
{
ssize_t ret;
/* Check if the descriptor is valid */
if (_descriptor_is_valid(descriptor) == false)
{
return MR_EINVAL;
}
/* Read the device */
ret = _device_read(descriptor, buf, count);
if (ret < 0)
{
goto _exit;
}
/* Return actual read size */
return ret;
_exit:
MR_LOG_E("Read '%d' failed: %s.\r\n", descriptor, mr_strerror((int)ret));
return ret;
}
/**
* @brief This function write data to a device.
*
* @param descriptor The descriptor of the device.
* @param buf The buffer to be written.
* @param count The count of write.
*
* @return The size of the actual write, otherwise an error code.
*
* @note If the async operation is successful, the device will not be released
* until the operation is complete.
*/
ssize_t mr_device_write(int descriptor, const void *buf, size_t count)
{
ssize_t ret;
/* Check if the descriptor is valid */
if (_descriptor_is_valid(descriptor) == false)
{
return MR_EINVAL;
}
/* Write the device */
ret = _device_write(descriptor, buf, count);
if (ret < 0)
{
goto _exit;
}
/* Return actual write size */
return ret;
_exit:
MR_LOG_E("Write '%d' failed: %s.\r\n", descriptor, mr_strerror((int)ret));
return ret;
}
/**
* @brief This function ioctl a device.
*
* @param descriptor The descriptor of the device.
* @param cmd The command of the ioctl.
* @param args The arguments of the ioctl.
*
* @return The size of the actual ioctl, otherwise an error code.
*/
int mr_device_ioctl(int descriptor, int cmd, void *args)
{
int ret;
/* Check if the descriptor is valid */
if (_descriptor_is_valid(descriptor) == false)
{
return MR_EINVAL;
}
/* Ioctl the device */
ret = _device_ioctl(descriptor, cmd, args);
if (ret < 0)
{
goto _exit;
}
/* Return actual ioctl size */
return ret;
_exit:
MR_LOG_E("Ioctl '%d' failed: %s.\r\n", descriptor, mr_strerror(ret));
return ret;
}