Last active
January 15, 2020 00:38
-
-
Save georgemorgan/7fc653ffcf14e7e25346eed042d863de to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
The purpose of the implementation is to drive an event subsystem that allows events that arise on any of the attached platforms to trigger | |
callbacks on other attached platforms, called observers. | |
*/ | |
/* Message system block diagram. | |
ls_msg_send -> lf_endpoint_enqueue -> if the endpoint is not asynchronous (as is dma), then the message is sent out immediately | |
lf_msg_recieve -> wait until the message has been marked recieved | |
*/ | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <stdbool.h> | |
#include <string.h> | |
#include <pthread.h> | |
#include <unistd.h> | |
/* Helps identify which functions are device specific. */ | |
#define __DEVICE_FUNC__ | |
/* - ERROR MACHINERY - */ | |
#define lf_success 1 | |
#define lf_failure 0 | |
#define lf_assert(truth) if (!(truth)) { return lf_failure; } | |
typedef uint32_t lf_error; | |
/* - LINKED LIST IMPLEMENTATION - */ | |
struct _lf_ll { | |
/* The item data. */ | |
void *item; | |
/* A deconstructor for the item, if any. */ | |
int (* deconstructor)(void *item); | |
/* The next item in the list. */ | |
struct _lf_ll *next; | |
}; | |
/* Appends an item to the linked list. */ | |
int lf_ll_append(struct _lf_ll **_ll, void *item, void *deconstructor) { | |
lf_assert(_ll); | |
struct _lf_ll *new = malloc(sizeof(struct _lf_ll)); | |
lf_assert(new); | |
memset(new, 0, sizeof(struct _lf_ll)); | |
new -> item = item; | |
new -> deconstructor = deconstructor; | |
struct _lf_ll *head = *_ll; | |
if (head) { | |
while (head -> next) { | |
head = head -> next; | |
} | |
head -> next = new; | |
} else { | |
*_ll = new; | |
} | |
return lf_success; | |
} | |
void *lf_ll_apply_func(struct _lf_ll *_ll, void *_other, void *(* func)(void *_item, void *_other)) { | |
struct _lf_ll *ll = _ll; | |
lf_assert(ll); | |
do { | |
void *result = NULL; | |
/* Apply the function to the current item in the list. */ | |
result = func(ll -> item, _other); | |
/* If the function returns something, return it here. */ | |
if (result) { | |
return result; | |
} | |
} while ((ll = ll -> next)); | |
return NULL; | |
} | |
/* Releases the entire linked list. */ | |
int lf_ll_release(struct _lf_ll **_ll) { | |
lf_assert(_ll); | |
struct _lf_ll *ll = *_ll; | |
lf_assert(ll); | |
do { | |
/* Call the item's deconstructor. */ | |
if (ll -> deconstructor) { | |
ll -> deconstructor(ll -> item); | |
} | |
struct _lf_ll *old = ll; | |
ll = ll -> next; | |
free(old); | |
} while (ll); | |
*_ll = NULL; | |
return lf_success; | |
} | |
void *lf_ll_pop(struct _lf_ll **_ll) { | |
lf_assert(_ll); | |
struct _lf_ll *ll = *_ll; | |
lf_assert(ll); | |
*_ll = ll -> next; | |
void *item = ll -> item; | |
free(ll); | |
*_ll = NULL; | |
return item; | |
} | |
/* - MESSAGE SYSTEM - */ | |
typedef enum { | |
lf_msg_rpc_kind, | |
lf_msg_event_kind, | |
} lf_msg_kind; | |
typedef uint32_t lf_event_id; | |
struct _lf_msg *lf_msg_create(lf_msg_kind kind); | |
/* A message with intent to modify device state. */ | |
struct _lf_msg { | |
/* The raw data conveyed by the message. */ | |
void *_raw; | |
/* The size of the raw data conveyed by the message. */ | |
/* The kind of message. */ | |
lf_msg_kind kind; | |
/* The outgoing message's number. */ | |
int message_number; | |
/* The receipt event id. */ | |
lf_event_id event_id; | |
}; | |
/* A response message indicating a return value and error state of parsing the message. */ | |
struct _lf_response { | |
/* The return value of parsing the message. */ | |
uint32_t retval; | |
/* The error code generated by performing the message intent. */ | |
lf_error error_code; | |
}; | |
typedef struct _lf_ll lf_msg_queue; | |
struct _lf_endpoint { | |
/* Releases the endpoint. */ | |
int (* release)(struct _lf_endpoint *self); | |
/* A queue of outgoing messages. */ | |
lf_msg_queue *outgoing; | |
/* A queue of incoming messages ready to be handled. */ | |
lf_msg_queue *incoming; | |
}; | |
struct _lf_response lf_msg_send(struct _lf_msg *msg, struct _lf_endpoint *endpoint); | |
#define LF_DEVICE_NAME_MAX 16 | |
struct _lf_device { | |
/* The device's plaintext name. */ | |
char name[LF_DEVICE_NAME_MAX]; | |
/* The endpoint via which the device can be contacted. */ | |
struct _lf_endpoint *endpoint; | |
}; | |
/* Enqueues a message for sending over the endpoint. */ | |
int lf_endpoint_enqueue(struct _lf_endpoint *endpoint, struct _lf_msg *message) { | |
return lf_ll_append(&(endpoint -> incoming), message, free); | |
} | |
bool lf_endpoint_has_data(struct _lf_endpoint *endpoint) { | |
return true; | |
} | |
/* Dequeues the next message to be sent. */ | |
struct _lf_msg *lf_endpoint_dequeue(struct _lf_endpoint *endpoint) { | |
return lf_ll_pop(&(endpoint -> incoming)); | |
} | |
/* Checks to see if any messages are available over the endpoint, and loads them into the incoming queue if there are. This function should never block! */ | |
void lf_endpoint_poll(struct _lf_endpoint *endpoint) { | |
/* Checks if there is any data available over this endpoint. If there is, it is loaded into the message processing queue. */ | |
} | |
int lf_endpoint_release(struct _lf_endpoint *endpoint) { | |
lf_assert(endpoint); | |
/* Release any configuration specific to the endpoint. */ | |
endpoint -> release(endpoint); | |
free(endpoint); | |
return lf_success; | |
} | |
/* - EVENT SYSTEM - */ | |
struct _lf_observer { | |
/* The event id which this observer is subscribed to. */ | |
lf_event_id event_id; | |
/* The endpoint over which the observer is to be notified. */ | |
struct _lf_endpoint *endpoint; | |
}; | |
typedef struct _lf_ll *lf_observer_list; | |
struct _lf_observer *lf_observer_create(lf_event_id _id, struct _lf_endpoint *_endpoint) { | |
struct _lf_observer *observer = malloc(sizeof(struct _lf_observer)); | |
lf_assert(observer); | |
observer -> event_id = _id; | |
observer -> endpoint = _endpoint; | |
return observer; | |
} | |
struct _lf_event_info { | |
/* The event that ocurred. */ | |
struct _lf_event *event; | |
/* The device that the event occurred on. */ | |
struct _lf_device *device; | |
/* The error code that was generated after performing the event. */ | |
lf_error error; | |
}; | |
typedef void (* lf_event_handler)(struct _lf_event *event); | |
struct _lf_event { | |
/* The event's identifier. */ | |
lf_event_id id; | |
/* The event's handler function, if any. */ | |
lf_event_handler handler; | |
/* The event info that is to be passed to the event handler. */ | |
struct _lf_device *device; | |
/* The event context pointer. */ | |
void *ctx; | |
/* A linked list of observers subscribed to this event. */ | |
lf_observer_list observers; | |
}; | |
/* - LIBFLIPPER MACHINERY - */ | |
typedef struct _lf_ll *lf_device_list; | |
typedef struct _lf_ll *lf_event_list; | |
/* LIBFLIPPER GLOBAL STATE */ | |
static lf_device_list lf_attached_devices; | |
static struct _lf_device *lf_current_device; | |
static lf_event_list lf_registered_events; | |
/* Returns the event list. */ | |
#define lf_get_event_list() lf_registered_events | |
/* Gets the head of the device linked list, the first device attached. */ | |
#define lf_get_device_list() lf_attached_devices | |
struct _lf_device *lf_device_create(struct _lf_endpoint *_endpoint) { | |
struct _lf_device *device = malloc(sizeof(struct _lf_device)); | |
lf_assert(device); | |
memset(device, 0, sizeof(struct _lf_endpoint)); | |
device -> endpoint = _endpoint; | |
return device; | |
} | |
/* Returns the active device. */ | |
struct _lf_device *lf_get_current_device(void) { | |
lf_assert(lf_current_device); | |
return lf_current_device; | |
} | |
/* Detaches a device from libflipper. */ | |
int lf_detach(struct _lf_device *device) { | |
lf_assert(device); | |
/* Release the device's endpoint. */ | |
lf_endpoint_release(device -> endpoint); | |
return lf_success; | |
} | |
/* Attempts to attach to all unattached devices. Returns how many devices were attached. */ | |
int lf_attach(void) { | |
return 0; | |
} | |
/* Registers an endpoint with the libflipper over which devices may be attached. */ | |
int lf_register_endpoint(struct _lf_endpoint *endpoint) { | |
return lf_success; | |
} | |
/* Deactivates libflipper state and releases the event loop. */ | |
void lf_finish(void) { | |
/* Release all of the events. */ | |
lf_ll_release(&lf_get_event_list()); | |
/* Release all of the attached devices. */ | |
lf_ll_release(&lf_get_device_list()); | |
} | |
/* - CARBON HAL MACHINERY - */ | |
/* Attaches to a carbon device. */ | |
int flipper_attach(void) { | |
/* Create the carbon endpoint. */ | |
struct _lf_endpoint *carbon_endpoint = NULL; | |
/* Register the carbon edition endpoint with libflipper. */ | |
lf_register_endpoint(carbon_endpoint); | |
/* Attach to all available Carbon Edition devices. */ | |
return lf_attach(); | |
} | |
struct _lf_event *lf_event_create(lf_event_id _id, lf_event_handler handler, void *_ctx) { | |
struct _lf_event *event = malloc(sizeof(struct _lf_event)); | |
lf_assert(event); | |
memset(event, 0, sizeof(struct _lf_event)); | |
event -> id = _id; | |
event -> handler = handler; | |
event -> ctx = _ctx; | |
return event; | |
} | |
/* Returns an unused event id. */ | |
lf_event_id lf_event_generate_unique_id(void) { | |
return 0; | |
} | |
typedef struct _lf_event lf_event; | |
/* Tears down an event and its observers, removing it from the event system. */ | |
int lf_event_release(lf_event *event) { | |
lf_assert(event); | |
/* Tear down all of the observers registered to this event. */ | |
lf_ll_release(&(event -> observers)); | |
free(event); | |
return lf_success; | |
} | |
/* Registers an event with the event system and assigns its handler. This is done on ALL platforms that interact with a given event identifier. */ | |
lf_event *lf_event_register(lf_event_id id, lf_event_handler handler, void *ctx) { | |
struct _lf_event *event = lf_event_create(id, handler, ctx); | |
lf_assert(event); | |
lf_ll_append(&lf_get_event_list(), event, lf_event_release); | |
return event; | |
} | |
void *lf_event_finder(void *_event, void *_id) { | |
struct _lf_event *event = _event; | |
lf_event_id id = (lf_event_id)_id; | |
if (event -> id == id) { | |
return event; | |
} | |
return NULL; | |
} | |
struct _lf_event *lf_event_for_id(lf_event_id id) { | |
void *id_as_void_ptr = (void *)(uintptr_t)id; | |
return lf_ll_apply_func(lf_get_event_list(), id_as_void_ptr, lf_event_finder); | |
} | |
/* Registers the endpoint over which the last message was recieved as an observer to an event. */ | |
int __DEVICE_FUNC__ lf_observer_register(struct _lf_endpoint *endpoint, lf_event_id id) { | |
lf_assert(endpoint); | |
struct _lf_observer *observer = lf_observer_create(id, endpoint); | |
lf_assert(observer); | |
/* Obtain the event that we are registering this observer to. */ | |
struct _lf_event *event = lf_event_for_id(id); | |
lf_assert(event); | |
return lf_ll_append(&(event -> observers), observer, free); | |
} | |
/* Causes a local event to be triggered when events of the same identifier are triggered on the device. */ | |
int lf_event_subscribe(lf_event *event, struct _lf_device *device) { | |
lf_assert(event && device); | |
/* Register the link on the device. */ | |
event -> device = device; | |
/* CALL TO DEVICE HERE */ | |
lf_observer_register(device -> endpoint, event -> id); | |
return lf_success; | |
} | |
void *lf_observer_notify(void *_observer, void *_unused) { | |
struct _lf_observer *observer = _observer; | |
lf_assert(observer); | |
/* Send a message to the observer to notify it that an event was triggered. */ | |
struct _lf_msg *msg = lf_msg_create(lf_msg_event_kind); | |
msg -> event_id = observer -> event_id; | |
lf_msg_send(msg, observer -> endpoint); | |
return NULL; | |
} | |
/* Triggers an event causing its observers to be notified. */ | |
int lf_event_trigger(lf_event *event) { | |
/* Trigger all of the event's observers. */ | |
lf_ll_apply_func(event -> observers, NULL, lf_observer_notify); | |
/* If there is a callback, call it. */ | |
if (event -> handler) { | |
event -> handler(event); | |
} | |
return lf_success; | |
} | |
/* - MESSAGE MACHINERY - */ | |
/* Creates a stub message body for a given kind of message. */ | |
struct _lf_msg *lf_msg_create(lf_msg_kind kind) { | |
struct _lf_msg *msg = malloc(sizeof(struct _lf_msg)); | |
lf_assert(msg); | |
memset(msg, 0, sizeof(struct _lf_msg)); | |
msg -> kind = kind; | |
msg -> event_id = 0; | |
return msg; | |
} | |
/* Sends a message to a specified device. This function blocks until the response message has been received. */ | |
struct _lf_response lf_msg_send(struct _lf_msg *msg, struct _lf_endpoint *endpoint) { | |
/* Send the message's raw data over the device's endpoint. */ | |
/* Obtain the response message. */ | |
struct _lf_response response = { 0 }; | |
/* Return the reponse. */ | |
return response; | |
} | |
/* Creates a message receipt event for the outgoing packet. */ | |
int lf_msg_subscribe_receipt(struct _lf_msg *msg, lf_event_handler callback) { | |
/* Generate a unique id to receive the message receipt event over. */ | |
lf_event_id id = lf_event_generate_unique_id(); | |
lf_event_register(id, callback, NULL); | |
/* Set the message's recepit event id. */ | |
msg -> event_id = id; | |
return lf_success; | |
} | |
/* Asynchronously sends a message to the given device. Invokes the callback function with the response message when the message returns. */ | |
int lf_msg_send_async(struct _lf_msg *msg, struct _lf_endpoint *endpoint, lf_event_handler callback) { | |
if (callback) { | |
/* Subscribe the handler provided to the message's response. */ | |
lf_msg_subscribe_receipt(msg, callback); | |
} | |
/* Queue the message to be sent over the device's endpoint. */ | |
lf_endpoint_enqueue(endpoint, msg); | |
return lf_success; | |
} | |
/* Handles a message locally. */ | |
int lf_msg_apply(struct _lf_msg *msg) { | |
lf_assert(msg); | |
/* Switch on the message type. */ | |
switch (msg -> kind) { | |
case lf_msg_event_kind: | |
/* If the message has a callback event, trigger it. */ | |
if (msg -> event_id) { | |
struct _lf_event *receipt_event = lf_event_for_id(msg -> event_id); | |
if (!receipt_event) { | |
break; | |
} | |
lf_event_trigger(receipt_event); | |
} | |
break; | |
case lf_msg_rpc_kind: | |
/* Send to the RPC handler here. */ | |
break; | |
default: | |
return lf_failure; | |
break; | |
} | |
return lf_success; | |
} | |
/* This function is called for each of the attached devices. */ | |
void *lf_event_handler_func(void *_device, void *_other) { | |
struct _lf_device *device = _device; | |
lf_assert(device); | |
/* Handle all of the messages available over the device's endpoint. */ | |
while (lf_endpoint_has_data(device -> endpoint)) { | |
/* Dequeue a message from the endpoint's incoming message queue. */ | |
struct _lf_msg *msg = lf_endpoint_dequeue(device -> endpoint); | |
lf_assert(msg); | |
/* Apply the message to the world. */ | |
lf_msg_apply(msg); | |
} | |
return NULL; | |
} | |
void lf_handle_events(void) { | |
for (;;) { | |
/* Handle events across all attached devices. */ | |
lf_ll_apply_func(lf_get_device_list(), NULL, lf_event_handler_func); | |
/* Avoid tail-call optimization. */ | |
__asm__ __volatile__ (""); | |
} | |
} | |
/* - EVENT MACHINERY TEST - */ | |
/* MY_EVENT_H */ | |
enum { | |
null_id, | |
my_event_a_id | |
} my_event_ids; | |
/* HOST IMPLEMENTATION */ | |
void my_event_a_callback(struct _lf_event *event) { | |
/* Once the event is triggered on the device, this code should be run on the host. */ | |
printf("You triggered 'my_event_a' on the device %s.\n", event -> device -> name); | |
} | |
/* DEVICE IMPLEMENTATION */ | |
void __DEVICE_FUNC__ device_imp(void) { | |
/* The event is created on the device and registered to a callback function. */ | |
lf_event *event = lf_event_register(my_event_a_id, NULL, NULL); | |
/* The event is triggered on the device. */ | |
lf_event_trigger(event); | |
} | |
struct _lf_device *my_device_a; | |
/* Some other thread function. */ | |
void *other_thread(void *info) { | |
while (1) { | |
/* Enqueue an event trigger message. */ | |
struct _lf_msg *msg = lf_msg_create(lf_msg_event_kind); | |
msg -> event_id = 1; | |
lf_endpoint_enqueue(my_device_a -> endpoint, msg); | |
usleep(1000000); | |
} | |
return NULL; | |
} | |
int main(int argc, const char * argv[]) { | |
/* Do logic to configure devices. */ | |
flipper_attach(); | |
my_device_a = lf_device_create(NULL); | |
my_device_a -> endpoint = malloc(sizeof(struct _lf_endpoint)); | |
memset(my_device_a -> endpoint, 0, sizeof(struct _lf_endpoint)); | |
strcpy(my_device_a -> name, "device a"); | |
lf_ll_append(&lf_get_device_list(), my_device_a, lf_detach); | |
/* Create an event that we would like to notify a callback when triggered. */ | |
lf_event *event = lf_event_register(my_event_a_id, my_event_a_callback, NULL); | |
/* Link the event to device A. If this event occurs on device A, the callback registered above will be invoked. */ | |
lf_event_subscribe(event, my_device_a); | |
/* Do some pthread stuff to get another thread to enqueue messages for a test. */ | |
pthread_t t; | |
pthread_create(&t, NULL, other_thread, NULL); | |
/* Handle events incoming from the device. This routine does not return. */ | |
lf_handle_events(); | |
return EXIT_FAILURE; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment