Files
mango/dev/core.c
Max Wash 3233169f25 dev: implement reading from block devices
reading from block devices is done using the block cache (bcache).
This cache stores sectors from a block device in pages of memory
marked as 'cached', which will allow them to be reclaimed when
memory pressure is high (TODO).

while block device drivers implement callbacks allowing reading/writing
at block-granularity, the device subsystem uses the block cache to
implement reading/writing at byte-granularity in a driver-agnostic way.

block drivers can disable the block cache for their devices, but this
will require that any clients communicate with the devices at
block-granularity.

also added an offset parameter to device and object read/write functions/callbacks.
2023-07-09 21:58:40 +01:00

298 lines
7.6 KiB
C

#include <socks/status.h>
#include <socks/object.h>
#include <socks/device.h>
#include <socks/printk.h>
#include <socks/bitmap.h>
#include <socks/libc/stdio.h>
#define DEVICE_CAST(p) OBJECT_C_CAST(struct device, dev_base, &device_type, p)
static struct object *dev_folder = NULL;
static struct device *__root_device = NULL;
static struct device *__misc_device = NULL;
static kern_status_t device_object_destroy(struct object *);
static kern_status_t device_object_read(struct object *obj, void *, size_t, size_t *, socks_flags_t);
static kern_status_t device_object_write(struct object *obj, const void *, size_t, size_t *, socks_flags_t);
static kern_status_t device_object_query_name(struct object *, char out[OBJECT_NAME_MAX]);
static kern_status_t device_object_get_child_at(struct object *, size_t, struct object **);
static kern_status_t device_object_get_child_named(struct object *, const char *, struct object **);
extern kern_status_t init_driver_tree(void);
extern struct device_type_ops char_type_ops;
extern struct device_type_ops block_type_ops;
extern struct device_type_ops input_type_ops;
extern struct device_type_ops framebuffer_type_ops;
extern struct device_type_ops bus_type_ops;
static struct device_type_ops *type_ops[] = {
[DEV_TYPE_UNKNOWN] = NULL,
[DEV_TYPE_BLOCK] = &block_type_ops,
[DEV_TYPE_CHAR] = &char_type_ops,
[DEV_TYPE_NET] = NULL,
[DEV_TYPE_INPUT] = &input_type_ops,
[DEV_TYPE_BUS] = &bus_type_ops,
[DEV_TYPE_FRAMEBUFFER] = &framebuffer_type_ops,
};
static struct object_type device_type = {
.ob_name = "device",
.ob_size = sizeof(struct device),
.ob_ops = {
.read = device_object_read,
.write = device_object_write,
.destroy = device_object_destroy,
.query_name = device_object_query_name,
.get_at = device_object_get_child_at,
.get_named = device_object_get_child_named,
}
};
static kern_status_t set_root_device(struct device *dev)
{
if (__root_device) {
set_remove_object(dev_folder, &__root_device->dev_base);
object_deref(&__root_device->dev_base);
}
object_ref(&dev->dev_base);
set_add_object(dev_folder, &dev->dev_base);
__root_device = dev;
return KERN_OK;
}
kern_status_t device_init(void)
{
object_type_register(&device_type);
dev_folder = set_create("dev");
object_publish(global_namespace(), "/", dev_folder);
kern_status_t status = init_driver_tree();
if (status != KERN_OK) {
return status;
}
struct bus_device *system_dev = bus_device_create();
struct device *system_dev_base = bus_device_base(system_dev);
snprintf(system_dev_base->dev_name, sizeof system_dev_base->dev_name, "system");
set_root_device(bus_device_base(system_dev));
struct bus_device *misc_dev = bus_device_create();
struct device *misc_dev_base = bus_device_base(misc_dev);
snprintf(misc_dev_base->dev_name, sizeof misc_dev_base->dev_name, "misc");
__misc_device = misc_dev_base;
struct driver *system = system_driver();
device_register(__root_device, system, NULL);
device_register(__misc_device, system, __root_device);
return KERN_OK;
}
struct device *root_device(void)
{
return __root_device;
}
struct device *misc_device(void)
{
return __misc_device;
}
struct device *device_alloc(void)
{
struct object *dev_object = object_create(&device_type);
if (!dev_object) {
return NULL;
}
return DEVICE_CAST(dev_object);
}
struct device *generic_device_create(void)
{
struct device *dev = device_alloc();
if (!dev) {
return NULL;
}
dev->dev_type = DEV_TYPE_UNKNOWN;
return dev;
}
kern_status_t device_read(struct device *dev, void *buf, size_t offset, size_t size, size_t *bytes_read, socks_flags_t flags)
{
kern_status_t status = KERN_UNSUPPORTED;
if (type_ops[dev->dev_type] && type_ops[dev->dev_type]->read) {
status = type_ops[dev->dev_type]->read(dev, buf, offset, size, bytes_read, flags);
}
return status;
}
kern_status_t device_write(struct device *dev, const void *buf, size_t offset, size_t size, size_t *bytes_written, socks_flags_t flags)
{
kern_status_t status = KERN_UNSUPPORTED;
if (type_ops[dev->dev_type] && type_ops[dev->dev_type]->write) {
status = type_ops[dev->dev_type]->write(dev, buf, offset, size, bytes_written, flags);
}
return status;
}
struct device *cast_to_device(struct object *obj)
{
return DEVICE_CAST(obj);
}
static kern_status_t device_object_read(struct object *obj, void *p, size_t offset, size_t *count, socks_flags_t flags)
{
struct device *dev = DEVICE_CAST(obj);
return device_read(dev, p, *count, offset, count, flags);
}
static kern_status_t device_object_write(struct object *obj, const void *p, size_t offset, size_t *count, socks_flags_t flags)
{
struct device *dev = DEVICE_CAST(obj);
return device_write(dev, p, *count, offset, count, flags);
}
static kern_status_t device_object_destroy(struct object *obj)
{
return KERN_OK;
}
static kern_status_t device_object_query_name(struct object *obj, char out[OBJECT_NAME_MAX])
{
struct device *dev = DEVICE_CAST(obj);
if (!dev) {
return KERN_INVALID_ARGUMENT;
}
strncpy(out, dev->dev_name, OBJECT_NAME_MAX - 1);
out[OBJECT_NAME_MAX - 1] = 0;
return KERN_OK;
}
static kern_status_t device_object_get_child_at(struct object *obj, size_t at, struct object **out)
{
struct device *dev = DEVICE_CAST(obj);
size_t i = 0;
queue_foreach(struct device, child, &dev->dev_children, dev_childent) {
if (i == at) {
*out = object_ref(&child->dev_base);
return KERN_OK;
}
i++;
}
return KERN_NO_ENTRY;
}
static kern_status_t device_object_get_child_named(struct object *obj, const char *name, struct object **out)
{
struct device *dev = DEVICE_CAST(obj);
if (!dev) {
return KERN_INVALID_ARGUMENT;
}
queue_foreach(struct device, child, &dev->dev_children, dev_childent) {
if (!strcmp(child->dev_name, name)) {
*out = object_ref(&child->dev_base);
return KERN_OK;
}
}
return KERN_NO_ENTRY;
}
static kern_status_t add_device_to_parent(struct device *dev, struct device *parent)
{
kern_status_t status = KERN_OK;
queue_foreach (struct device, child, &parent->dev_children, dev_childent) {
if (!strcmp(dev->dev_name, child->dev_name)) {
status = KERN_NAME_EXISTS;
break;
}
}
if (status != KERN_OK) {
return status;
}
queue_push_back(&parent->dev_children, &dev->dev_childent);
return KERN_OK;
}
kern_status_t device_register(struct device *dev, struct driver *owner, struct device *parent)
{
unsigned long flags;
device_lock_irqsave(dev, &flags);
if (dev->dev_owner) {
struct driver *prev_owner = dev->dev_owner;
/* migrate device to new driver */
driver_lock(prev_owner);
driver_remove_device(prev_owner, dev);
driver_free_minor(prev_owner, dev->dev_minor);
dev->dev_minor = DEV_MINOR_INVALID;
dev->dev_owner = NULL;
driver_unlock(prev_owner);
}
driver_lock(owner);
if (owner->drv_major == DEV_MAJOR_INVALID) {
driver_unlock(owner);
device_unlock_irqrestore(dev, flags);
/* TODO better error message for lack of resources? */
return KERN_INVALID_ARGUMENT;
}
unsigned int minor = driver_alloc_minor(owner);
if (minor == DEV_MINOR_INVALID) {
driver_unlock(owner);
device_unlock_irqrestore(dev, flags);
/* TODO better error message for lack of resources? */
return KERN_BUSY;
}
kern_status_t status = KERN_OK;
if (parent) {
status = add_device_to_parent(dev, parent);
}
if (status != KERN_OK) {
driver_unlock(owner);
device_unlock_irqrestore(dev, flags);
return status;
}
dev->dev_minor = minor;
dev->dev_owner = owner;
driver_add_device(owner, dev);
if (type_ops[dev->dev_type] && type_ops[dev->dev_type]->register_device) {
status = type_ops[dev->dev_type]->register_device(dev);
}
/* TODO remove device if registration failed */
driver_unlock(owner);
device_unlock_irqrestore(dev, flags);
return status;
}