Skip to content

Commit

Permalink
merge(kernel: devices): add a device/driver API
Browse files Browse the repository at this point in the history
We can now declare generic drivers!

This API is heavily inspired by that of Linux,
as it is the only one I know and am familiar with.
Plus I like it :)

Drivers are automatically loaded suring startup, and any
detected device that matches a driver is automatically
represented as a device, which will then be used to interact
with the hardware.

The actual implementation of how to interact with the hardware
is not the responsibility of the driver, and depends on the type of
device. This should generally be represented as a VTable inside a
struct that contains a basic device struct.

Signed-off-by: Léo DUBOIN <[email protected]>
  • Loading branch information
d4ilyrun committed Jun 14, 2024
2 parents 79d7113 + ae3f979 commit 7b39957
Show file tree
Hide file tree
Showing 16 changed files with 404 additions and 113 deletions.
92 changes: 59 additions & 33 deletions include/kernel/device.h
Original file line number Diff line number Diff line change
@@ -1,55 +1,81 @@
#pragma once

/**
*
* @brief Kernel Device/Driver API
*
* @defgroup kernel_device Devices
* @ingroup kernel
*
* # Devices
*
* ## Philsophy
*
* This device driver API is heavily inspired by Linux's one, as it is the only
* one I'm familiar with currently, and I like it.
*
* Hardware interactions are split into two 3 parts:
* * The physical hardware (not our responsibility)
* * The driver
* * The device
*
* **Every interaction with a hardware component is done by interacting with its
* corresponding device.**
*
*
* ## Design
*
* Devices come in many shapes (timers, buses, etc ...), and as such, the sets
* of functions used to communicate with a given hardware varies a lot.
* The different function groups (vtables) should be located inside the
* \c device struct (whichever one, depending on the type of hardware),
* and should stay hidden the driver.
*
* The driver's only responsibility is to create, initialize and register the
* appropriate device \ref device for its hardware. Once the device has been
* registered, it can be used to communicate with the underlying hardware.
*
*
* ## Usage
*
* The kernel automatically detects and keeps track of all the connected
* hardware devices. When a new device is detected, its corresponding driver is
* looked for in the list of curenlty loaded drivers. If a matching one has been
* found, it is requested to create the appropriate device.
*
* @see \ref kernel_device_driver
*
* @{
*/

#pragma once

#include <kernel/error.h>
#include <kernel/types.h>

#include <libalgo/linked_list.h>

#include <stddef.h>

#include "kernel/error.h"
typedef struct device_driver driver_t;

/** @struct device
* @brief Represents a device
*
* In the current implementation, as everything is stored in RAM, the device
* struct is simply a wrapper arround what could be considered a contiguous
* buffer in memory.
*
* In the future though, the device structure should become a common interface
* between devices of different types (e.g. ram and hard disks should share this
* API)
* @brief Represents a device inside the kernel
*/
typedef struct device {
u32 start; ///< Start address of the device in memory
size_t size; ///< Size of the memory area for this device

/** @struct device_operations
* @brief VTable of I/O operations for the device
*/
struct device_operations {
error_t (*read)(const struct device *, char *buffer, size_t offset,
size_t size);
error_t (*write)(const struct device *, size_t offset,
const char *buffer, size_t size);
} operations;
} dev_t;

/** Create a new device
*
* @warning This implementation only serves as a place holder for the actual
* one in order to ease the migration once it is implemneted. This API
* **WILL** change.
*
* @param start The starting address of the device's memory range
* @param size The size of teh device's memory range

node_t this; ///< Used to list devices, internal use only

char *name; ///< The name of the device
driver_t *driver; ///< The driver for this device

} device_t;

/** Register a new device.
*
* The device must have been allocated and initialized first by its
* corresponding driver.
*/
dev_t *device_new(u32 start, size_t size);
error_t device_register(device_t *);

/** @} */
7 changes: 7 additions & 0 deletions include/kernel/devices/acpi.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
* @{
*/

#pragma once

#include <kernel/devices/driver.h>
#include <kernel/error.h>

#include <multiboot.h>
Expand All @@ -38,4 +41,8 @@
*/
error_t acpi_init(struct multiboot_info *mbt);

/**
*/
void acpi_start_devices(void);

/** @} */
116 changes: 116 additions & 0 deletions include/kernel/devices/driver.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/**
* @brief Driver API
*
* @defgroup kernel_device_driver Drivers
* @ingroup kernel_device
*
* # Drivers
*
* This file describes the **driver** part of the Device/Driver API.
*
* ## Creating a driver
*
* When creating a new driver, you MUST declare it using \ref DECLARE_DRIVER.
* All drivers declared using this macro are automatically loaded when starting
* up the kernel.
*
* The kernel automatically detects and keeps track of all the connected
* hardware devices. When a new device is detected, its corresponding driver is
* looked for inside this list of loaded drivers. If a match is found, the
* matching driver's \c probe function is called to create the correct type of
* device, and register it.
*
* @see kernel_device
*
* @{
*/

#pragma once

#include <kernel/device.h>
#include <kernel/error.h>

#include <libalgo/linked_list.h>
#include <utils/compiler.h>

/** The different ways a device can be detected
* @enum device_detection_method
*/
typedef enum device_detection_method {
DRIVER_TYPE_ACPI, ///< Devices detected through ACPI tables
DRIVER_TYPE_TOTAL_COUNT,
} device_detection_method;

/** The basic device driver structure
* @struct device_driver
*/
typedef struct device_driver {

node_t this; ///< Intrusive list node used to iterate through loaded drivers
const char *name; ///< The name of the driver

/** Information used by the driver API to match a device against a driver
* @struct driver_match
*/
struct driver_match {
/// How matching devices are expected to be detected
device_detection_method method;
/// The matching data.
/// This can be a name, a path or anything depending on the detection
/// method.
const char *const *compatible;
} match; ///< Information used to match devices against this driver

/** Vector table of the common operations used to control drivers
* @struct driver_operations
*/
struct driver_operations {
/// Bind the driver to the device
/// @param name The name of the matching physical device
/// @param addr The physical address of the device
error_t (*probe)(const char *, paddr_t addr);
} operations; ///< Vector table of driver control operations

} driver_t;

/** Register a new driver
*
* After a driver has been registerd, any newly detected device that it matches
* will automatically be bound to it using the \c probe function.
*
* @info This function is generally called automatically during startup on the
* declared drivers, and should not need to be called manually elsewhere.
*/
void driver_register(driver_t *driver);

typedef void (*driver_init_t)(void);

/** Declare a new driver.
*
* Drivers declared using this macro are automatically loaded at startup, and
* are automatically associated with their corresponding devices.
*
* @param _name The name of the driver
* @param _driver The driver's definition (\ref device_driver)
*/
#define DECLARE_DRIVER(_name, _driver) \
static void init_driver_##_name(void) \
{ \
driver_register(_driver); \
} \
\
SECTION(".data.driver.init") \
MAYBE_UNUSED \
static driver_init_t __##_name##_driver_init = init_driver_##_name;

/** Load all builtin drivers.
* Builtin drivers are ones declared using \ref DECLARE_DRIVER
*/
void driver_load_drivers(void);

/** Retreive the driver that matches the given arguments.
* @return A pointer to the driver, or one containing an eventual error code.
*/
const driver_t *driver_find_match(device_detection_method, const char *);

/** @} */
1 change: 1 addition & 0 deletions include/kernel/error.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ typedef enum error {
E_SUCCESS, ///< No error
E_NOENT = 2, ///< No such file or directory
E_NOMEM = 12, ///< Out of memory
E_NODEV = 19, ///< No such device
E_INVAL = 22, ///< Invalid argument
E_NOT_IMPLEMENTED = 38, ///< Function not implemented
E_NOT_SUPPORTED = 95, ///< Operation not supported
Expand Down
3 changes: 3 additions & 0 deletions include/kernel/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,8 @@ typedef u64 timestamp_t;
* @return 0 if both are equal, -1 if left is inferior, +1 if it is superior
*/
typedef int (*compare_t)(const void *left, const void *right);
#define COMPARE_EQ 0
#define COMPARE_LESS -1
#define COMPARE_GREATER 1

#endif /* KERNEL_TYPES_H */
26 changes: 15 additions & 11 deletions include/kernel/vfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
* @{
*/

#include <kernel/device.h>
#include <kernel/error.h>
#include <kernel/types.h>

Expand Down Expand Up @@ -72,7 +71,6 @@ typedef struct vfs_operations {
*/
typedef struct vfs {
node_t this;
dev_t *dev; ///< Device used by this filesystem
vfs_ops_t *operations; ///< @ref vfs_operations
vnode_t *node; ///< vnode on which this FS is mounted
void *pdata; ///< Private FS dependent data
Expand All @@ -83,26 +81,32 @@ typedef struct vfs {
* @param path Where the fs should be mounted (must be an existing
* directory)
* @param fs_type The name of the filesystem to mount
* @param device The device where the filesystem is stored
*
* TODO: TEMPORARY: Should be replaced with a device
* @param start Start of the filesystem's address range
* @param end End of the filesystem's address range
*
* @return
* * E_INVAL - the filesystem does not exist
* * E_INVAL - the path does not lead to a directory
* * E_INVAL - another filesystem is mounted at the requested path
* * E_NOENT - the path is invalid
*/
error_t vfs_mount(const char *path, const char *fs_type, dev_t *);
error_t vfs_mount(const char *path, const char *fs_type, u32, u32);

/** Mount a filesystem at the root of the VFS.
*
* @param fs_type The type of the filesystem
* @param device The device where the filesystem is stored
* @param fs_type The type of the filesystem
*
* TODO: TEMPORARY: Should be replaced with a device
* @param start Start of the filesystem's address range
* @param end End of the filesystem's address range
*
* @return
* @return
* * E_INVAL - there already is a root for the VFS
* * E_INVAL - the filesystem does not exist
*/
error_t vfs_mount_root(const char *fs_type, dev_t *);
error_t vfs_mount_root(const char *fs_type, u32 start, u32 end);

/** Unmount the filesystem present at the given path.
*
Expand Down Expand Up @@ -224,9 +228,9 @@ vnode_t *vfs_vnode_release(vnode_t *);
* This structure is used by the VFS driver to mount the filesystem.
*/
typedef ALIGNED(ARCH_WORD_SIZE) struct vfs_fs {
const char *const name; ///< Name of the filesystem
vfs_t *(*new)(dev_t *); ///< Create a new instance of this filesystem using
///< the given device
const char *const name; ///< Name of the filesystem
vfs_t *(*new)(u32, u32); ///< Create a new instance of this filesystem
///< using the given device
} vfs_fs_t;

/** Declare a new available filesystem.
Expand Down
Loading

0 comments on commit 7b39957

Please sign in to comment.