UMP Channel implementation
This commit is contained in:
parent
937c6fb701
commit
07f8cfe2bc
@ -21,7 +21,7 @@
|
||||
|
||||
// allow access to the toradex board
|
||||
"runArgs": ["--privileged"],
|
||||
// "mounts": ["type=bind,src=/dev/bus/usb,dst=/dev/bus/usb"],
|
||||
"mounts": ["type=bind,src=/dev/bus/usb,dst=/dev/bus/usb"],
|
||||
|
||||
"remoteUser": "vscode",
|
||||
}
|
||||
|
||||
251
include/aos/ump_chan.h
Normal file
251
include/aos/ump_chan.h
Normal file
@ -0,0 +1,251 @@
|
||||
/**
|
||||
* \file
|
||||
* \brief bidirectional communication through shared memory
|
||||
*/
|
||||
|
||||
#ifndef _INIT_UMP_CHAN_H_
|
||||
#define _INIT_UMP_CHAN_H_
|
||||
|
||||
#include <aos/aos.h>
|
||||
|
||||
/**
|
||||
* @brief Number of data bytes in a single buffer of the ring buffer
|
||||
*/
|
||||
#define UMP_RING_BUF_DATA_SIZE (CACHE_LINE_SIZE - 1)
|
||||
|
||||
struct ump_send_queue_entry;
|
||||
|
||||
typedef void (*ump_recv_header_callback_fn_t)(void *arg, size_t header_size, void *header, size_t payload_size);
|
||||
typedef void (*ump_recv_payload_callback_fn_t)(void *arg, size_t payload_size, void *payload);
|
||||
typedef void (*ump_send_callback_fn_t)(void *arg, void *payload, struct ump_send_queue_entry *entry);
|
||||
|
||||
/**
|
||||
* @brief Specifies which role an endpoint would like to take in the UMP protocol
|
||||
*
|
||||
* This is just used to agree on which part of the shared frame should be used
|
||||
* to send data and which to receive data
|
||||
*/
|
||||
enum ump_role {
|
||||
UMP_ROLE_SERVER,
|
||||
UMP_ROLE_CLIENT,
|
||||
UMP_ROLE_COUNT, // How many roles exist
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Stores metadata about a ring buffer
|
||||
*/
|
||||
struct ump_ring_state {
|
||||
/**
|
||||
* @brief first entry of the ring buffer
|
||||
*/
|
||||
struct ump_ring_buf_entry *ring_start;
|
||||
/**
|
||||
* @brief Number of entries in the ring buffer
|
||||
*/
|
||||
size_t ring_entry_count;
|
||||
/**
|
||||
* @brief Which entry should be read or written next
|
||||
*/
|
||||
size_t ring_entry_next;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief State of a send queue entry
|
||||
*
|
||||
* Used to keep track of which part of the message we are
|
||||
* currently sending
|
||||
*/
|
||||
enum ump_send_entry_state {
|
||||
UMP_SEND_ENTRY_STATE_START_MESSAGE,
|
||||
UMP_SEND_ENTRY_STATE_HEADER,
|
||||
UMP_SEND_ENTRY_STATE_PAYLOAD,
|
||||
UMP_SEND_ENTRY_STATE_COUNT, // How many states exist
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief An entry in the sending queue of a channel.
|
||||
*
|
||||
* Contains all metadata information needed to send this message
|
||||
*/
|
||||
struct ump_send_queue_entry {
|
||||
enum ump_send_entry_state state;
|
||||
|
||||
size_t header_size;
|
||||
void *header;
|
||||
|
||||
size_t payload_size;
|
||||
void *payload;
|
||||
|
||||
ump_send_callback_fn_t callback;
|
||||
void *callback_arg;
|
||||
|
||||
/**
|
||||
* @brief first this will refer to the header and when done with that
|
||||
* it will be updated to refer to the payload
|
||||
*/
|
||||
size_t send_bytes_left;
|
||||
void *send_buf_position;
|
||||
|
||||
struct ump_send_queue_entry *next;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief object containing metadata required for sending a message on a channel
|
||||
*/
|
||||
struct ump_send_chan {
|
||||
struct ump_ring_state ring_state;
|
||||
|
||||
// required to hold this lock for any changes to the send_queue
|
||||
struct thread_mutex send_queue_lock;
|
||||
struct ump_send_queue_entry *send_queue_head;
|
||||
struct ump_send_queue_entry *send_queue_tail;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Used to keep track of which part of a message was received or is being received
|
||||
*
|
||||
*/
|
||||
enum recv_state {
|
||||
UMP_RECV_STATE_IDLE,
|
||||
UMP_RECV_STATE_START_MESSAGE,
|
||||
UMP_RECV_STATE_HEADER,
|
||||
UMP_RECV_STATE_PAYLOAD_IDLE,
|
||||
UMP_RECV_STATE_PAYLOAD,
|
||||
UMP_RECV_STATE_COUNT, // How many states exist
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Metadata for the receiving side of the channel
|
||||
*
|
||||
*/
|
||||
struct ump_recv_chan {
|
||||
struct ump_ring_state ring_state;
|
||||
|
||||
/**
|
||||
* @brief This must be held when editing this object
|
||||
* from a thread that is not the worker thread
|
||||
*/
|
||||
struct thread_mutex recv_register_lock;
|
||||
|
||||
enum recv_state state;
|
||||
|
||||
/**
|
||||
* @brief Current read state.
|
||||
* This will first refer to the header and afterwards to the payload
|
||||
*/
|
||||
size_t read_bytes_left;
|
||||
void *read_buf_position;
|
||||
|
||||
size_t header_size;
|
||||
void *header;
|
||||
size_t next_payload_size;
|
||||
void *payload;
|
||||
|
||||
ump_recv_header_callback_fn_t recv_header_callback;
|
||||
ump_recv_payload_callback_fn_t recv_payload_callback;
|
||||
void *callback_arg;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A single entry in a ring buffer
|
||||
*/
|
||||
struct ump_ring_buf_entry {
|
||||
/**
|
||||
* @brief data carried by this ring buffer entry
|
||||
*/
|
||||
uint8_t data[UMP_RING_BUF_DATA_SIZE];
|
||||
/**
|
||||
* @brief specifies who currently owns this buffer entry.
|
||||
* 0 -> sender
|
||||
* 1 -> receiver
|
||||
*/
|
||||
volatile uint8_t owner;
|
||||
};
|
||||
// for now we want each entry to be cache line sized
|
||||
STATIC_ASSERT_SIZEOF(struct ump_ring_buf_entry, CACHE_LINE_SIZE);
|
||||
|
||||
/**
|
||||
* @brief The first data block for a new message.
|
||||
* This is only needed for the payload size right now
|
||||
*/
|
||||
struct ump_start_message {
|
||||
size_t payload_size;
|
||||
// NOTE rueegges: we could remove this field since the reciever also has
|
||||
// to specify it but for now it is here to ensure there are no missmatches
|
||||
// in sizes on the two endpoints
|
||||
size_t header_size;
|
||||
};
|
||||
STATIC_ASSERT(sizeof(struct ump_start_message) <= sizeof(struct ump_ring_buf_entry), "struct ump_start_message is too big");
|
||||
|
||||
/**
|
||||
* @brief Initialize the send and receive objects for a channel.
|
||||
*
|
||||
* @param role Specifies if this side should be the server or client in the connection
|
||||
* @param send_chan send channel object to be allocated and initialized
|
||||
* @param recv_chan receive channel object to be allocated and initialized
|
||||
* @param recv_header_size size of the headers that will be received
|
||||
* @param shared_frame the frame capability shared between the client and server
|
||||
* @param run_on_waitset the waitset on which the async tasks should run. if it is NULL a thread will be started to handle the tasks
|
||||
* @return errval_t
|
||||
*/
|
||||
errval_t ump_chan_init(
|
||||
enum ump_role role,
|
||||
struct ump_send_chan **send_chan,
|
||||
struct ump_recv_chan **recv_chan,
|
||||
size_t recv_header_size,
|
||||
struct capref shared_frame,
|
||||
struct waitset *run_on_waitset
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Send a message on the channel
|
||||
*
|
||||
* @param chan channel to send on
|
||||
* @param entry an allocated object to carry the send information
|
||||
* @param header_size size of the header to be sent
|
||||
* @param header buffer containing the header to be sent
|
||||
* @param payload_size size of the payload to be sent
|
||||
* @param payload buffer containing the payload to be sent
|
||||
* @param callback function to call after sending
|
||||
* @param callback_arg argument to pass to the callback function
|
||||
*/
|
||||
void ump_send (
|
||||
struct ump_send_chan *chan,
|
||||
struct ump_send_queue_entry *entry,
|
||||
size_t header_size,
|
||||
void *header,
|
||||
size_t payload_size,
|
||||
void *payload,
|
||||
ump_send_callback_fn_t callback,
|
||||
void *callback_arg
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Receive the header of the next message. Must only be called on a fresh channel or after the receive payload completed
|
||||
*
|
||||
* @param chan channel to listen on
|
||||
* @param callback function to call with the header and the size of the payload
|
||||
* @param callback_arg argument to pass to the callback function
|
||||
*/
|
||||
void ump_recv_header (
|
||||
struct ump_recv_chan *chan,
|
||||
ump_recv_header_callback_fn_t callback,
|
||||
void *callback_arg
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Receive the payload of the next message. Must only be called after the receive header completed
|
||||
*
|
||||
* @param chan channel to listen on
|
||||
* @param payload buffer to store the payload in. must be at least as large as the payload_size argument in the corresponding callback from receive header. if this is NULL then the payload will be dropped
|
||||
* @param callback function to call with payload
|
||||
* @param callback_arg argument to pass to the callback function
|
||||
*/
|
||||
void ump_recv_payload (
|
||||
struct ump_recv_chan *chan,
|
||||
void *payload,
|
||||
ump_recv_payload_callback_fn_t callback,
|
||||
void *callback_arg
|
||||
);
|
||||
|
||||
#endif /* _INIT_UMP_CHAN_H_ */
|
||||
@ -26,6 +26,7 @@
|
||||
"aos_rpc.c",
|
||||
"aos_urpc.c",
|
||||
"ump_binding.c",
|
||||
"ump_chan.c",
|
||||
"performance.c",
|
||||
"capabilities.c",
|
||||
"coreset.c",
|
||||
|
||||
@ -528,7 +528,6 @@ errval_t paging_init_params(struct spawn_domain_params *params)
|
||||
// pt_print_state(¤t);
|
||||
}
|
||||
|
||||
// TODO rueegges: not sure this is sufficient?
|
||||
err = thread_set_exception_handler(pt_exception_handler, NULL, (void*) pt_static_exception_stack, (void*) pt_static_exception_stack + PT_STATIC_EXCEPTION_STACK_SIZE, NULL, NULL);
|
||||
if (err_is_fail(err)) {
|
||||
return err_push(err, LIB_ERR_VREGION_PAGEFAULT_HANDLER);
|
||||
|
||||
477
lib/aos/ump_chan.c
Normal file
477
lib/aos/ump_chan.c
Normal file
@ -0,0 +1,477 @@
|
||||
#include <aos/aos.h>
|
||||
#include <aos/ump_chan.h>
|
||||
#include <aos/paging.h>
|
||||
#include <aos/waitset_chan.h>
|
||||
|
||||
/**
|
||||
* @brief initializes a ring buffer state
|
||||
*
|
||||
* @param ring_state object to initialize
|
||||
* @param buf start of the ring buffer memory
|
||||
* @param buf_len size of the ring buffer memory
|
||||
*/
|
||||
static void ump_ring_init(struct ump_ring_state *ring_state, void *buf, size_t buf_len) {
|
||||
// the ring buffers should be cache-line aligned
|
||||
assert(((uintptr_t) buf) % CACHE_LINE_SIZE == 0);
|
||||
|
||||
ring_state->ring_start = buf;
|
||||
ring_state->ring_entry_next = 0;
|
||||
ring_state->ring_entry_count = buf_len / sizeof(struct ump_ring_buf_entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Tries to write a ring buffer entry without blocking. Advances in the ring if successful
|
||||
*
|
||||
* @param ring_state ring to write to
|
||||
* @param buf start of the buffer containing the data
|
||||
* @param bytes number of bytes from the buffer to write to the ring
|
||||
* @return true successfully wrote the entry
|
||||
* @return false failed to write the entry because the buffer is full
|
||||
*/
|
||||
static bool ump_ring_try_write_next(struct ump_ring_state *ring_state, void *buf, size_t bytes) {
|
||||
assert(bytes <= UMP_RING_BUF_DATA_SIZE);
|
||||
assert(buf != NULL);
|
||||
|
||||
// check if we own the entry
|
||||
if (ring_state->ring_start[ring_state->ring_entry_next].owner) {
|
||||
return false;
|
||||
}
|
||||
// memory barrier because the other endpoint might be on another core
|
||||
__asm volatile (
|
||||
"dmb sy\n"
|
||||
);
|
||||
|
||||
memcpy(&ring_state->ring_start[ring_state->ring_entry_next].data, buf, bytes);
|
||||
|
||||
// memory barrier because the other endpoint might be on another core
|
||||
__asm volatile (
|
||||
"dmb sy\n"
|
||||
);
|
||||
// mark the entry as owned by the receiver
|
||||
ring_state->ring_start[ring_state->ring_entry_next].owner = 1;
|
||||
// advance in the ring
|
||||
ring_state->ring_entry_next = (ring_state->ring_entry_next + 1) % ring_state->ring_entry_count;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Tries to read an entry of the ring buffer. Advances the ring if successful
|
||||
*
|
||||
* @param ring_state ring to read from
|
||||
* @param buf start of the buffer to write to. if NULL the data will be dropped
|
||||
* @param bytes number of bytes to read from this buffer (from the start of the buffer. the rest is dropped)
|
||||
* @return true successfully read an entry
|
||||
* @return false could not read an entry because no data was available
|
||||
*/
|
||||
static bool ump_ring_try_read_next(struct ump_ring_state *ring_state, void *buf, size_t bytes) {
|
||||
assert(bytes <= UMP_RING_BUF_DATA_SIZE);
|
||||
|
||||
// check if we own the entry
|
||||
if(!ring_state->ring_start[ring_state->ring_entry_next].owner) {
|
||||
return false;
|
||||
}
|
||||
// memory barrier because the other endpoint might be on another core
|
||||
__asm volatile (
|
||||
"dmb sy\n"
|
||||
);
|
||||
|
||||
// if we get a NULL buf then we need to drop the data
|
||||
if (buf != NULL) {
|
||||
memcpy(buf, &ring_state->ring_start[ring_state->ring_entry_next].data, bytes);
|
||||
}
|
||||
// memory barrier because the other endpoint might be on another core
|
||||
__asm volatile (
|
||||
"dmb sy\n"
|
||||
);
|
||||
// mark the entry as owned by the sender
|
||||
ring_state->ring_start[ring_state->ring_entry_next].owner = 0;
|
||||
// advance in the ring
|
||||
ring_state->ring_entry_next = (ring_state->ring_entry_next + 1) % ring_state->ring_entry_count;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Try to read the next message. The message might also be read only partially but it will continue in the next call to this function
|
||||
*
|
||||
* @param recv_chan channel to read the message on
|
||||
*/
|
||||
static void ump_try_read(struct ump_recv_chan *recv_chan) {
|
||||
// TODO rueegges: is this locking required?!? if writing the state is a single assembly instruction then not, otherwise yes
|
||||
// make sure we own the read metadata, otherwise we have nothing to do anyways
|
||||
thread_mutex_lock(&recv_chan->recv_register_lock);
|
||||
if(
|
||||
recv_chan->state != UMP_RECV_STATE_START_MESSAGE &&
|
||||
recv_chan->state != UMP_RECV_STATE_HEADER &&
|
||||
recv_chan->state != UMP_RECV_STATE_PAYLOAD
|
||||
) {
|
||||
thread_mutex_unlock(&recv_chan->recv_register_lock);
|
||||
return;
|
||||
}
|
||||
thread_mutex_unlock(&recv_chan->recv_register_lock);
|
||||
|
||||
// first make sure we have read the start message
|
||||
if (recv_chan->state == UMP_RECV_STATE_START_MESSAGE) {
|
||||
struct ump_start_message start_message;
|
||||
if(!ump_ring_try_read_next(&recv_chan->ring_state, &start_message, sizeof(struct ump_start_message))) {
|
||||
return;
|
||||
}
|
||||
// the sender and receiver MUST agree on the header size
|
||||
assert(recv_chan->header_size == start_message.header_size);
|
||||
|
||||
// update the receive state to start reading the header
|
||||
recv_chan->read_bytes_left = recv_chan->header_size;
|
||||
recv_chan->read_buf_position = recv_chan->header;
|
||||
recv_chan->next_payload_size = start_message.payload_size;
|
||||
recv_chan->state = UMP_RECV_STATE_HEADER;
|
||||
}
|
||||
|
||||
// read as much as popssible of the header or payload
|
||||
while(recv_chan->read_bytes_left > 0) {
|
||||
size_t bytes_to_read = MIN(recv_chan->read_bytes_left, UMP_RING_BUF_DATA_SIZE);
|
||||
if(!ump_ring_try_read_next(&recv_chan->ring_state, recv_chan->read_buf_position, bytes_to_read)) {
|
||||
// buffer is empty, we need to try again later
|
||||
return;
|
||||
}
|
||||
if(recv_chan->read_buf_position != NULL) {
|
||||
recv_chan->read_buf_position += bytes_to_read;
|
||||
}
|
||||
recv_chan->read_bytes_left -= bytes_to_read;
|
||||
}
|
||||
|
||||
// we managed to read the current stage, advance the state and call the callbacks
|
||||
if (recv_chan->state == UMP_RECV_STATE_HEADER) {
|
||||
recv_chan->state = UMP_RECV_STATE_PAYLOAD_IDLE;
|
||||
recv_chan->recv_header_callback(
|
||||
recv_chan->callback_arg,
|
||||
recv_chan->header_size,
|
||||
recv_chan->header,
|
||||
recv_chan->next_payload_size
|
||||
);
|
||||
} else if (recv_chan->state == UMP_RECV_STATE_PAYLOAD) {
|
||||
recv_chan->state = UMP_RECV_STATE_IDLE;
|
||||
recv_chan->recv_payload_callback(
|
||||
recv_chan->callback_arg,
|
||||
recv_chan->next_payload_size,
|
||||
recv_chan->payload
|
||||
);
|
||||
} else {
|
||||
// cannot happen but just to be sure...
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Try to send the next message. The message might also be sent only partially but it will continue in the next call to this function
|
||||
*
|
||||
* @param send_chan channel to send the message on
|
||||
*/
|
||||
static void ump_try_send(struct ump_send_chan *send_chan) {
|
||||
// TODO rueegges: is this locking required?!? if writing the head is a single assembly instruction then not, otherwise yes
|
||||
// check if there is anything to do.
|
||||
thread_mutex_lock(&send_chan->send_queue_lock);
|
||||
if (send_chan->send_queue_head == NULL) {
|
||||
thread_mutex_unlock(&send_chan->send_queue_lock);
|
||||
return;
|
||||
}
|
||||
thread_mutex_unlock(&send_chan->send_queue_lock);
|
||||
|
||||
// get the entry to handle next
|
||||
struct ump_send_queue_entry *entry = send_chan->send_queue_head;
|
||||
|
||||
// first we need to send the start message so the receiver knows how much payload to expect
|
||||
if (entry->state == UMP_SEND_ENTRY_STATE_START_MESSAGE) {
|
||||
struct ump_start_message start_message = {
|
||||
.header_size = entry->header_size,
|
||||
.payload_size = entry->payload_size,
|
||||
};
|
||||
if(!ump_ring_try_write_next(&send_chan->ring_state, &start_message, sizeof(struct ump_start_message))) {
|
||||
// ring is full, we need to retry later
|
||||
return;
|
||||
}
|
||||
entry->state = UMP_SEND_ENTRY_STATE_HEADER;
|
||||
entry->send_buf_position = entry->header;
|
||||
entry->send_bytes_left = entry->header_size;
|
||||
}
|
||||
|
||||
// send as much in the current stage as possible
|
||||
while(entry->send_bytes_left > 0) {
|
||||
size_t bytes_to_send = MIN(entry->send_bytes_left, UMP_RING_BUF_DATA_SIZE);
|
||||
if(!ump_ring_try_write_next(&send_chan->ring_state, entry->send_buf_position, bytes_to_send)) {
|
||||
return;
|
||||
}
|
||||
entry->send_buf_position += bytes_to_send;
|
||||
entry->send_bytes_left -= bytes_to_send;
|
||||
}
|
||||
|
||||
// we managed to send the current stage, advance the state!
|
||||
if (entry->state == UMP_SEND_ENTRY_STATE_HEADER) {
|
||||
entry->send_buf_position = entry->payload;
|
||||
entry->send_bytes_left = entry->payload_size;
|
||||
entry->state = UMP_SEND_ENTRY_STATE_PAYLOAD;
|
||||
} else if (entry->state == UMP_SEND_ENTRY_STATE_PAYLOAD) {
|
||||
// we are done sending, remove the entry from the queue
|
||||
thread_mutex_lock(&send_chan->send_queue_lock);
|
||||
// we take the current send queue state to get "next" because it might have changed since
|
||||
// we read the entry (NULL -> something)
|
||||
if (send_chan->send_queue_head->next == NULL) {
|
||||
send_chan->send_queue_tail = NULL;
|
||||
}
|
||||
send_chan->send_queue_head = send_chan->send_queue_head->next;
|
||||
thread_mutex_unlock(&send_chan->send_queue_lock);
|
||||
|
||||
// send the callback
|
||||
assert(entry->callback != NULL);
|
||||
entry->callback(entry->callback_arg, entry->payload, entry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Contains references to information required by the ump channel worker
|
||||
*/
|
||||
struct ump_worker_context {
|
||||
struct waitset *run_on_waitset;
|
||||
struct waitset_chanstate waitset_chan;
|
||||
struct ump_recv_chan *recv_chan;
|
||||
struct ump_send_chan *send_chan;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Do the async work on the channel
|
||||
*
|
||||
* @param arg
|
||||
*/
|
||||
static void ump_work(void *arg) {
|
||||
struct ump_worker_context *worker_context = arg;
|
||||
|
||||
// check the recv queue
|
||||
ump_try_read(worker_context->recv_chan);
|
||||
|
||||
// check the send queue
|
||||
ump_try_send(worker_context->send_chan);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief used to handle the async work on a separate thread
|
||||
*
|
||||
* @param arg the worker context
|
||||
* @return int not used
|
||||
*/
|
||||
static int ump_thread_worker(void *arg) {
|
||||
|
||||
while (true) {
|
||||
|
||||
ump_work(arg);
|
||||
|
||||
thread_yield();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ump_waitset_worker(void *arg);
|
||||
/**
|
||||
* @brief Schedule the worker on its waitset.
|
||||
*
|
||||
* @param worker_context
|
||||
*/
|
||||
static void ump_worker_schedule(struct ump_worker_context *worker_context) {
|
||||
// we want to be automatically re-registered upon completion of an event
|
||||
// worker_context->waitset_chan.persistent = true;
|
||||
errval_t err = waitset_chan_trigger_closure(
|
||||
worker_context->run_on_waitset,
|
||||
&worker_context->waitset_chan,
|
||||
MKCLOSURE(ump_waitset_worker, worker_context)
|
||||
);
|
||||
// this is supposed to be called single threaded and only upon event trigger
|
||||
// hence this cannot be a reregister and thus not fail
|
||||
assert(err_is_ok(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief used to handle the async work on a waitset
|
||||
*
|
||||
* @param arg the worker context
|
||||
*/
|
||||
static void ump_waitset_worker(void *arg) {
|
||||
ump_work(arg);
|
||||
|
||||
// put back in the queue
|
||||
ump_worker_schedule(arg);
|
||||
}
|
||||
|
||||
errval_t ump_chan_init(
|
||||
enum ump_role role,
|
||||
struct ump_send_chan **send_chan_ret,
|
||||
struct ump_recv_chan **recv_chan_ret,
|
||||
size_t recv_header_size,
|
||||
struct capref shared_frame,
|
||||
struct waitset *run_on_waitset
|
||||
) {
|
||||
errval_t err;
|
||||
|
||||
// check that we have a valid role for the channel
|
||||
if (role >= UMP_ROLE_COUNT) return ERR_INVALID_ARGS;
|
||||
|
||||
// identify the shared frame to determine its size
|
||||
struct frame_identity shared_frame_id;
|
||||
err = frame_identify(shared_frame, &shared_frame_id);
|
||||
if (err_is_fail(err)) return LIB_ERR_FRAME_IDENTIFY;
|
||||
|
||||
// map the full frame
|
||||
void *shared_mem;
|
||||
err = paging_map_frame(get_current_paging_state(), &shared_mem, shared_frame_id.bytes, shared_frame);
|
||||
if (err_is_fail(err)) return err_push(err, LIB_ERR_PMAP_MAP);
|
||||
|
||||
// get the size of the rings for each direction
|
||||
size_t ring_size = shared_frame_id.bytes / 2;
|
||||
debug_printf("[ump_chan_init] Each channel is %lu bytes\n", ring_size);
|
||||
|
||||
// allocate the channel structures
|
||||
struct ump_send_chan *send_chan = malloc(sizeof(struct ump_send_chan));
|
||||
if(send_chan == NULL) {
|
||||
paging_unmap(get_current_paging_state(), shared_mem);
|
||||
return LIB_ERR_MALLOC_FAIL;
|
||||
}
|
||||
struct ump_recv_chan *recv_chan = malloc(sizeof(struct ump_recv_chan));
|
||||
if(recv_chan == NULL) {
|
||||
paging_unmap(get_current_paging_state(), shared_mem);
|
||||
free(send_chan);
|
||||
return LIB_ERR_MALLOC_FAIL;
|
||||
}
|
||||
|
||||
// decide on which part of the buffer is used for sending and receiving
|
||||
// give the server receiving side the first half of the frame
|
||||
void *receive_buf = role == UMP_ROLE_SERVER ? shared_mem : shared_mem + ring_size;
|
||||
// give the server sending side the second half of the frame
|
||||
void *send_buf = role == UMP_ROLE_SERVER ? shared_mem + ring_size : shared_mem;
|
||||
|
||||
|
||||
// initialize recv state
|
||||
recv_chan->header = malloc(recv_header_size);
|
||||
if (recv_chan->header == NULL) {
|
||||
paging_unmap(get_current_paging_state(), shared_mem);
|
||||
free(send_chan);
|
||||
free(recv_chan);
|
||||
return LIB_ERR_MALLOC_FAIL;
|
||||
}
|
||||
recv_chan->header_size = recv_header_size;
|
||||
recv_chan->state = UMP_RECV_STATE_IDLE;
|
||||
ump_ring_init(&recv_chan->ring_state, receive_buf, ring_size);
|
||||
thread_mutex_init(&recv_chan->recv_register_lock);
|
||||
|
||||
// initialize send state
|
||||
send_chan->send_queue_head = NULL;
|
||||
send_chan->send_queue_tail = NULL;
|
||||
ump_ring_init(&send_chan->ring_state, send_buf, ring_size);
|
||||
thread_mutex_init(&send_chan->send_queue_lock);
|
||||
|
||||
// allocate and initialize the worker state for handling the async work
|
||||
struct ump_worker_context *worker_context = malloc(sizeof(struct ump_worker_context));
|
||||
if(worker_context == NULL) {
|
||||
paging_unmap(get_current_paging_state(), shared_mem);
|
||||
free(send_chan);
|
||||
free(recv_chan);
|
||||
free(recv_chan->header);
|
||||
return LIB_ERR_MALLOC_FAIL;
|
||||
}
|
||||
worker_context->run_on_waitset = run_on_waitset;
|
||||
worker_context->recv_chan = recv_chan;
|
||||
worker_context->send_chan = send_chan;
|
||||
|
||||
|
||||
if (run_on_waitset != NULL) {
|
||||
// we need to run on a waitset so initialize it and schedule the worker on it
|
||||
waitset_chanstate_init(&worker_context->waitset_chan, CHANTYPE_OTHER);
|
||||
ump_worker_schedule(worker_context);
|
||||
} else {
|
||||
// we need to run on a dedicated thread so create it
|
||||
struct thread *t = thread_create(ump_thread_worker, worker_context);
|
||||
if (t == NULL) {
|
||||
paging_unmap(get_current_paging_state(), shared_mem);
|
||||
free(send_chan);
|
||||
free(recv_chan);
|
||||
free(recv_chan->header);
|
||||
free(worker_context);
|
||||
return LIB_ERR_THREAD_CREATE;
|
||||
}
|
||||
}
|
||||
|
||||
// return the channel metadata
|
||||
*send_chan_ret = send_chan;
|
||||
*recv_chan_ret = recv_chan;
|
||||
|
||||
return SYS_ERR_OK;
|
||||
}
|
||||
|
||||
void ump_send (
|
||||
struct ump_send_chan *chan,
|
||||
struct ump_send_queue_entry *entry,
|
||||
size_t header_size,
|
||||
void *header,
|
||||
size_t payload_size,
|
||||
void *payload,
|
||||
ump_send_callback_fn_t callback,
|
||||
void *callback_arg
|
||||
) {
|
||||
// initialize the queue entry
|
||||
entry->header_size = header_size;
|
||||
entry->header = header;
|
||||
entry->payload_size = payload_size;
|
||||
entry->payload = payload;
|
||||
entry->callback = callback;
|
||||
entry->callback_arg = callback_arg;
|
||||
entry->next = NULL;
|
||||
|
||||
// add the new entry to the end of the send queue
|
||||
thread_mutex_lock(&chan->send_queue_lock);
|
||||
if(chan->send_queue_tail != NULL) {
|
||||
chan->send_queue_tail->next = entry;
|
||||
} else {
|
||||
chan->send_queue_head = entry;
|
||||
}
|
||||
chan->send_queue_tail = entry;
|
||||
thread_mutex_unlock(&chan->send_queue_lock);
|
||||
}
|
||||
|
||||
void ump_recv_header (
|
||||
struct ump_recv_chan *chan,
|
||||
ump_recv_header_callback_fn_t callback,
|
||||
void *callback_arg
|
||||
) {
|
||||
thread_mutex_lock(&chan->recv_register_lock);
|
||||
// TODO rueegges: caller has to ensure it only registers at most once?! Or should we just make it a noop?
|
||||
assert(chan->state == UMP_RECV_STATE_IDLE);
|
||||
|
||||
chan->recv_header_callback = callback;
|
||||
chan->callback_arg = callback_arg;
|
||||
|
||||
// this must be last so we have written everything when the worker thread sees this state
|
||||
chan->state = UMP_RECV_STATE_START_MESSAGE;
|
||||
|
||||
thread_mutex_unlock(&chan->recv_register_lock);
|
||||
}
|
||||
|
||||
void ump_recv_payload (
|
||||
struct ump_recv_chan *chan,
|
||||
void *payload,
|
||||
ump_recv_payload_callback_fn_t callback,
|
||||
void *callback_arg
|
||||
) {
|
||||
thread_mutex_lock(&chan->recv_register_lock);
|
||||
// TODO rueegges: caller has to ensure it only registers at most once?! Or should we just make it a noop?
|
||||
assert(chan->state == UMP_RECV_STATE_PAYLOAD_IDLE);
|
||||
|
||||
chan->recv_payload_callback = callback;
|
||||
chan->callback_arg = callback_arg;
|
||||
|
||||
chan->read_bytes_left = chan->next_payload_size;
|
||||
chan->read_buf_position = payload;
|
||||
chan->payload = payload;
|
||||
|
||||
// this must be last so we have written everything when the worker thread sees this state
|
||||
chan->state = UMP_RECV_STATE_PAYLOAD;
|
||||
|
||||
thread_mutex_unlock(&chan->recv_register_lock);
|
||||
}
|
||||
@ -133,6 +133,9 @@ errval_t ump_binding_connect (
|
||||
);
|
||||
if (err_is_fail(err)) return err_push(err, LIB_ERR_PMAP_MAP);
|
||||
|
||||
// NOTE rueegges: the ring buffer MUST be initialized to all zero so
|
||||
// all ring buffer entries are initially owned by the
|
||||
// respective senders
|
||||
memset(frame_data, 0, UMP_FRAME_SIZE);
|
||||
|
||||
err = paging_unmap(get_current_paging_state(), (void *)frame_data);
|
||||
|
||||
@ -3,17 +3,47 @@
|
||||
#include <aos/aos.h>
|
||||
#include <aos/ump_binding.h>
|
||||
#include <aos/aos_rpc.h>
|
||||
#include <aos/ump_chan.h>
|
||||
|
||||
#define ECHO_TEST_MESSAGE "Hello Client! Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
||||
|
||||
__attribute__((__used__))
|
||||
static void handle_payload_recv(void *arg, size_t payload_size, void *payload) {
|
||||
printf("Echo server: Received payload(%lu): %.*s\n", payload_size, payload_size, payload);
|
||||
}
|
||||
|
||||
__attribute__((__used__))
|
||||
static void handle_header_recv(void *arg, size_t header_size, void *header, size_t payload_size) {
|
||||
printf("Echo server: Received header: %u\n", *(uint8_t *)header);
|
||||
printf("Echo server: Payload is %lu bytes\n", payload_size);
|
||||
void *payload = malloc(payload_size);
|
||||
assert(payload != NULL);
|
||||
ump_recv_payload(arg, payload, handle_payload_recv, NULL);
|
||||
}
|
||||
|
||||
__attribute__((__used__))
|
||||
static void handle_send(void *arg, void *payload, struct ump_send_queue_entry *entry) {
|
||||
printf("Echo server: Message sent!\n");
|
||||
}
|
||||
|
||||
static errval_t connect_server (void *arg, struct capref cap) {
|
||||
debug_printf("Echo server: incoming connection\n");
|
||||
errval_t err;
|
||||
|
||||
struct paging_state *pstate = get_current_paging_state();
|
||||
uint8_t *ump_data;
|
||||
errval_t err = paging_map_frame(pstate, (void **)&ump_data, BASE_PAGE_SIZE, cap);
|
||||
if (err_is_fail(err)) USER_PANIC_ERR(err, "Failed to map ump frame");
|
||||
printf("Echo server: incoming connection\n");
|
||||
|
||||
// TODO: Create an UMP channel
|
||||
ump_data[0] = 123;
|
||||
struct ump_send_chan *send_chan;
|
||||
struct ump_recv_chan *recv_chan;
|
||||
err = ump_chan_init(UMP_ROLE_SERVER, &send_chan, &recv_chan, 1, cap, get_default_waitset());
|
||||
if (err_is_fail(err)) return err;
|
||||
|
||||
uint8_t *value_to_send = malloc(sizeof(uint8_t));
|
||||
*value_to_send = 123;
|
||||
struct ump_send_queue_entry *entry = malloc(sizeof(struct ump_send_queue_entry));
|
||||
printf("Echo server: Sending Message\n");
|
||||
ump_send(send_chan, entry, 1, value_to_send, sizeof(ECHO_TEST_MESSAGE), ECHO_TEST_MESSAGE, handle_send, NULL);
|
||||
|
||||
printf("Echo server: Receiving Message\n");
|
||||
ump_recv_header(recv_chan, handle_header_recv, recv_chan);
|
||||
|
||||
return SYS_ERR_OK;
|
||||
}
|
||||
@ -22,12 +52,12 @@ int main (int argc, char *argv[]) {
|
||||
errval_t err;
|
||||
struct ump_binding_server server;
|
||||
|
||||
debug_printf("Echo server registering\n");
|
||||
printf("Echo server: registering\n");
|
||||
|
||||
err = ump_binding_register(&server, UMP_SERVER_ECHO, connect_server, NULL);
|
||||
if (err_is_fail(err)) USER_PANIC_ERR(err, "Failed to register UMP server");
|
||||
|
||||
debug_printf("Echo server listening\n");
|
||||
printf("Echo server: listening\n");
|
||||
|
||||
struct waitset *default_ws = get_default_waitset();
|
||||
while (true) {
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
#include <aos/aos.h>
|
||||
#include <aos/aos_rpc.h>
|
||||
#include <aos/deferred.h>
|
||||
#include <aos/ump_chan.h>
|
||||
|
||||
#define HELLO_CMD_CATCH "catch"
|
||||
#define HELLO_CMD_SPAWN "spawn"
|
||||
@ -28,6 +29,8 @@
|
||||
|
||||
#define HELLO_CMDLINE_READ_LEN 100
|
||||
|
||||
#define HELLO_TEST_MESSAGE "Hello Server! Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
||||
|
||||
__attribute__((__used__))
|
||||
static int null_dereference(void *ignored) {
|
||||
debug_printf("[null_dereference] Oh no! :O\n");
|
||||
@ -36,6 +39,25 @@ static int null_dereference(void *ignored) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
__attribute__((__used__))
|
||||
static void handle_payload_recv(void *arg, size_t payload_size, void *payload) {
|
||||
printf("Echo client: Received payload(%lu): %.*s\n", payload_size, payload_size, payload);
|
||||
}
|
||||
|
||||
__attribute__((__used__))
|
||||
static void handle_header_recv(void *arg, size_t header_size, void *header, size_t payload_size) {
|
||||
printf("Echo client: Received header: %u\n", *(uint8_t *)header);
|
||||
printf("Echo client: Payload is %lu bytes\n", payload_size);
|
||||
void *payload = malloc(payload_size);
|
||||
assert(payload != NULL);
|
||||
ump_recv_payload(arg, payload, handle_payload_recv, NULL);
|
||||
}
|
||||
|
||||
__attribute__((__used__))
|
||||
static void handle_send(void *arg, void *payload, struct ump_send_queue_entry *entry) {
|
||||
printf("Echo client: Message sent!\n");
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
errval_t err;
|
||||
@ -133,7 +155,7 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
|
||||
if (argc > 1 && !strncmp(argv[1], HELLO_CMD_ECHOCLIENT, sizeof(HELLO_CMD_ECHOCLIENT))) {
|
||||
debug_printf("Connecting to echo server...\n");
|
||||
printf("Echo client: Connecting to echo server...\n");
|
||||
struct capref echo_cap;
|
||||
for (int attempts = 0; attempts < 50; attempts++) {
|
||||
err = aos_rpc_ump_connect(rpc, UMP_SERVER_ECHO, &echo_cap);
|
||||
@ -141,13 +163,24 @@ int main(int argc, char *argv[])
|
||||
barrelfish_usleep(100000);
|
||||
}
|
||||
if (err_is_fail(err)) USER_PANIC_ERR(err, "Failed to connect to echo server");
|
||||
debug_printf("Connected to echo server.\n");
|
||||
printf("Echo client: Connected to echo server.\n");
|
||||
|
||||
struct paging_state *pstate = get_current_paging_state();
|
||||
uint8_t *ump_data;
|
||||
err = paging_map_frame(pstate, (void **)&ump_data, BASE_PAGE_SIZE, echo_cap);
|
||||
if (err_is_fail(err)) USER_PANIC_ERR(err, "Failed to map ump frame");
|
||||
debug_printf("Read byte: %d\n", ump_data[0]);
|
||||
// Create the two uni-directional channels
|
||||
struct ump_send_chan *send_chan;
|
||||
struct ump_recv_chan *recv_chan;
|
||||
err = ump_chan_init(UMP_ROLE_CLIENT, &send_chan, &recv_chan, 1, echo_cap, NULL);
|
||||
if (err_is_fail(err)) return err;
|
||||
|
||||
// send a message to the server
|
||||
printf("Echo client: Sending Message\n");
|
||||
uint8_t *value_to_send = malloc(sizeof(uint8_t));
|
||||
*value_to_send = 37;
|
||||
struct ump_send_queue_entry *entry = malloc(sizeof(struct ump_send_queue_entry));
|
||||
ump_send(send_chan, entry, 1, value_to_send, sizeof(HELLO_TEST_MESSAGE), HELLO_TEST_MESSAGE, handle_send, NULL);
|
||||
|
||||
// try to receive and print a message from the server
|
||||
printf("Echo client: Receiving Message\n");
|
||||
ump_recv_header(recv_chan, handle_header_recv, recv_chan);
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user