| // |
| // Copyright (c) 2010-2026 Antmicro |
| // |
| // This file is licensed under MIT License. |
| // Full license text is available in 'licenses/MIT.txt' file. |
| // |
| #include "renode_api.h" |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #include <netdb.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/socket.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| #include <stdarg.h> |
| #include <stddef.h> |
| |
| #include "common.h" |
| #include "connection.h" |
| |
| struct renode { |
| renode_connection_t *conn; |
| }; |
| |
| struct renode_machine { |
| renode_t *renode; |
| int32_t md; |
| }; |
| |
| struct renode_adc { |
| renode_machine_t *machine; |
| int32_t id; |
| }; |
| |
| struct renode_gpio { |
| renode_machine_t *machine; |
| int32_t id; |
| }; |
| |
| struct renode_can { |
| renode_machine_t *machine; |
| int32_t id; |
| }; |
| |
| struct renode_bus_context { |
| renode_machine_t *machine; |
| int32_t id; |
| }; |
| |
| struct renode_spi { |
| renode_machine_t *machine; |
| int32_t id; |
| void *user_data; |
| renode_spi_transmit_callback_t on_transmit; |
| renode_spi_finish_callback_t on_finish; |
| }; |
| |
| void renode_free_error(renode_error_t *error) |
| { |
| if (error == NO_ERROR) { |
| return; |
| } |
| |
| if (error->flags & ERROR_FREE_MESSAGE) { |
| free(error->message); |
| } |
| free(error); |
| } |
| |
| void renode_set_fatal_error_callback(renode_t *renode, void *user_data, renode_fatal_error_callback_t callback) |
| { |
| renode_connection_set_fatal_error_callback(renode->conn, callback, user_data); |
| } |
| |
| #define ERRMSG_UNEXPECTED_RESPONSE_PAYLOAD_SIZE "Received unexpected number of bytes" |
| #define ERRMSG_COMMAND_MISMATCH "Received mismatched command" |
| |
| #define REQUEST_HEADER(cmd) {&(const message_payload_t){ cmd, TYPE_REQUEST }, sizeof(message_payload_t)} |
| #define RESPONSE_HEADER(cmd, type) {&(const message_payload_t){ cmd, type }, sizeof(message_payload_t)} |
| |
| #define assert_response(x, msg) do { if (unlikely(!(x))) { fprintf(stderr, "Assert not met in %s:%d: %s\n", __FILE__, __LINE__, #x); return renode_connection_send_message(conn, id, RESPONSE_HEADER(cmd, TYPE_ERROR), {msg, sizeof(msg)}); } } while (0) |
| |
| static renode_error_t *generic_response_handler(renode_connection_t *conn, const void *response, size_t size, void *ud); |
| static renode_error_t *generic_request_handler(renode_connection_t *conn, uint16_t id, const void *response, size_t size, void *ud); |
| static renode_error_t *parse_response(const void *response, size_t size, message_payload_t *header, const void **data, uint32_t *data_size); |
| |
| static renode_error_t *check_version_handler(renode_connection_t *conn, const void *response, size_t size, void *ud) |
| { |
| (void)conn; |
| (void)ud; |
| |
| message_payload_t header; |
| const void *data; |
| uint32_t data_size; |
| |
| return_error_if_fails(parse_response(response, size, &header, &data, &data_size)); |
| if(header.type != TYPE_SUCCESS) { |
| return create_error(ERR_COMMAND_FAILED, "Unexpected status on a CHECK_VERSION response: 0x%"PRIx8, header.type); |
| } |
| return NO_ERROR; |
| } |
| |
| renode_error_t *renode_connect(const char *port, renode_t **renode) |
| { |
| assert(port != NULL && renode != NULL); |
| |
| renode_connection_t *conn; |
| return_error_if_fails(renode_connection_open(&conn, &(renode_connection_config_t) { |
| .address = "localhost", |
| .port = port, |
| .request_callback = generic_request_handler, |
| .server_request_ud = NULL, |
| })); |
| |
| renode_error_t *err = renode_connection_send_request(conn, check_version_handler, NULL, |
| REQUEST_HEADER(CHECK_VERSION), |
| {&PROTOCOL_VERSION, sizeof(PROTOCOL_VERSION)}, |
| ); |
| |
| if (err != NO_ERROR) { |
| renode_connection_close(conn, /*blocking=*/true); |
| return err; |
| } |
| |
| *renode = xmalloc(sizeof(renode_t)); |
| (*renode)->conn = conn; |
| |
| return NO_ERROR; |
| } |
| |
| renode_error_t *renode_disconnect(renode_t **renode) |
| { |
| return renode_disconnect_ex(renode, /*blocking=*/true); |
| } |
| |
| renode_error_t *renode_disconnect_ex(renode_t **renode, bool blocking) |
| { |
| assert(renode != NULL && *renode != NULL); |
| |
| return_error_if_fails(renode_connection_close((*renode)->conn, blocking)); |
| free(*renode); |
| *renode = NULL; |
| |
| return NO_ERROR; |
| } |
| |
| #define MAX_CALLBACK_COUNT 1024 |
| |
| typedef void (*raw_callback_t)(void *, void *); |
| static raw_callback_t callbacks[MAX_CALLBACK_COUNT]; |
| static void *callback_user_data[MAX_CALLBACK_COUNT]; |
| static int32_t callbacks_count; |
| |
| static renode_error_t *register_callback(raw_callback_t callback, void *user_data, int32_t *ed) |
| { |
| assert_msg(callbacks_count < MAX_CALLBACK_COUNT, "Cannot register any more callbacks"); |
| |
| callbacks[callbacks_count] = callback; |
| callback_user_data[callbacks_count] = user_data; |
| |
| *ed = callbacks_count; |
| callbacks_count += 1; |
| |
| return NO_ERROR; |
| } |
| |
| // SPI event types (server -> client, in the event payload's first byte), must match SpiEvent in SPI.cs |
| typedef enum { |
| SPI_EVENT_TRANSMIT = 0, |
| SPI_EVENT_FINISH_TRANSMISSION = 1, |
| } spi_event_t; |
| |
| static renode_error_t *invoke_callback(renode_connection_t *conn, uint16_t id, api_command_t cmd, int32_t ed, const void *data, size_t size) |
| { |
| assert_response(ed < callbacks_count, "Tried to invoke callback for an invalid event descriptor"); |
| switch(cmd) |
| { |
| case GPIO: |
| assert_response(size == sizeof(renode_gpio_event_data_t), ERRMSG_UNEXPECTED_RESPONSE_PAYLOAD_SIZE); |
| |
| renode_gpio_event_data_t event_data; |
| memcpy(&event_data, data, sizeof(event_data)); |
| |
| callbacks[ed](callback_user_data[ed], &event_data); |
| return renode_connection_send_message(conn, id, RESPONSE_HEADER(cmd, TYPE_SUCCESS)); |
| case CAN_BUS: |
| assert_response(size >= sizeof(renode_can_event_data_t), ERRMSG_UNEXPECTED_RESPONSE_PAYLOAD_SIZE); |
| assert_response(size <= sizeof(renode_can_event_data_t) + MAX_CAN_FRAME_SIZE, ERRMSG_UNEXPECTED_RESPONSE_PAYLOAD_SIZE); |
| |
| renode_can_event_data_t *can_data; |
| can_data = xmalloc(size); |
| memcpy(can_data, data, size); |
| |
| callbacks[ed](callback_user_data[ed], can_data); |
| assert_response(size == sizeof(renode_can_event_data_t) + can_data->packet_length, ERRMSG_UNEXPECTED_RESPONSE_PAYLOAD_SIZE); |
| renode_error_t *result = renode_connection_send_message(conn, id, RESPONSE_HEADER(cmd, TYPE_SUCCESS)); |
| free(can_data); |
| return result; |
| case TIME_ELAPSED_CALLBACK: |
| assert_response(size == sizeof(renode_time_t), ERRMSG_UNEXPECTED_RESPONSE_PAYLOAD_SIZE); |
| |
| renode_time_t timestamp; |
| memcpy(×tamp, data, sizeof(timestamp)); |
| |
| callbacks[ed](callback_user_data[ed], ×tamp); |
| return renode_connection_send_message(conn, id, RESPONSE_HEADER(cmd, TYPE_SUCCESS)); |
| case SPI: |
| assert_response(size >= 1, ERRMSG_UNEXPECTED_RESPONSE_PAYLOAD_SIZE); |
| renode_spi_t *spi = callback_user_data[ed]; |
| |
| switch(((uint8_t*)data)[0]) |
| { |
| case SPI_EVENT_TRANSMIT: |
| assert_response(size == 2, ERRMSG_UNEXPECTED_RESPONSE_PAYLOAD_SIZE); |
| |
| uint8_t miso = spi->on_transmit(spi->user_data, ((uint8_t*)data)[1]); |
| return renode_connection_send_message(conn, id, RESPONSE_HEADER(cmd, TYPE_SUCCESS), {&miso, sizeof(miso)}); |
| case SPI_EVENT_FINISH_TRANSMISSION: |
| if (spi->on_finish) { |
| spi->on_finish(spi->user_data); |
| } |
| return renode_connection_send_message(conn, id, RESPONSE_HEADER(cmd, TYPE_SUCCESS)); |
| default: |
| return create_fatal_error_static("Received unknown SPI event type"); |
| } |
| default: |
| return renode_connection_send_message(conn, id, RESPONSE_HEADER(cmd, TYPE_INVALID_COMMAND)); |
| } |
| } |
| |
| static renode_error_t *get_machine_handler(renode_connection_t *conn, const void *data, size_t size, void *ud) |
| { |
| (void)conn; |
| |
| int32_t *id = ud; |
| assert_msg(size == 4, ERRMSG_UNEXPECTED_RESPONSE_PAYLOAD_SIZE); |
| memcpy(id, data, 4); |
| return NO_ERROR; |
| } |
| |
| renode_error_t *renode_get_machine(renode_t *renode, const char *name, renode_machine_t **machine) |
| { |
| int32_t id; |
| uint32_t name_length = strlen(name); |
| return_error_if_fails(renode_connection_send_request(renode->conn, generic_response_handler, &id, |
| REQUEST_HEADER(GET_MACHINE), |
| {&name_length, sizeof(name_length)}, |
| {name, name_length}, |
| )); |
| |
| assert_msg(id >= 0, "received invalid machine descriptor"); |
| |
| *machine = xmalloc(sizeof(renode_machine_t)); |
| (*machine)->renode = renode; |
| (*machine)->md = id; |
| |
| return NO_ERROR; |
| } |
| |
| static const char *time_unit_to_string(renode_time_unit_t unit) |
| { |
| switch (unit) { |
| case TU_NANOSECONDS: |
| return "ns"; |
| case TU_MICROSECONDS: |
| return "us"; |
| case TU_MILLISECONDS: |
| return "ms"; |
| case TU_SECONDS: |
| return "s"; |
| } |
| return "<unknown unit>"; |
| } |
| |
| renode_error_t *renode_create_time(uint64_t value, renode_time_unit_t unit, renode_time_t *time) |
| { |
| uint64_t result; |
| switch (unit) { |
| case TU_NANOSECONDS: |
| case TU_MICROSECONDS: |
| case TU_MILLISECONDS: |
| case TU_SECONDS: |
| result = value * unit; |
| if (result / unit != value) { |
| return create_error(ERR_INVALID_ARGUMENT, "%"PRIu64"%s exceedes the maximum time value of %"PRIu64"ns", time, time_unit_to_string(unit), UINT64_MAX); |
| } |
| *time = result; |
| return NO_ERROR; |
| } |
| return create_error(ERR_INVALID_ARGUMENT, "Invalid time unit: %d", unit); |
| } |
| |
| uint64_t renode_time_to_time_unit(renode_time_t time, renode_time_unit_t unit) |
| { |
| switch(unit) { |
| case TU_NANOSECONDS: |
| case TU_MICROSECONDS: |
| case TU_MILLISECONDS: |
| case TU_SECONDS: |
| return time / unit; |
| } |
| assert_exit(!"Invalid time unit value"); |
| } |
| |
| double renode_time_to_seconds(renode_time_t time) |
| { |
| return (double)time / TU_SECONDS; |
| } |
| |
| static renode_error_t *get_instance_handler(renode_connection_t *conn, const void *response, size_t response_size, void *ud) |
| { |
| (void)conn; |
| |
| message_payload_t header; |
| const void *data; |
| uint32_t data_size; |
| |
| return_error_if_fails(parse_response(response, response_size, &header, &data, &data_size)); |
| |
| assert_msg(data_size == sizeof(int32_t), ERRMSG_UNEXPECTED_RESPONSE_PAYLOAD_SIZE); |
| memcpy(ud, data, sizeof(int32_t)); |
| return NO_ERROR; |
| } |
| |
| static renode_error_t *renode_get_instance_descriptor(renode_machine_t *machine, api_command_t api_command, const char *name, int32_t *instance_descriptor) |
| { |
| uint32_t name_length = strlen(name); |
| *instance_descriptor = -1; |
| |
| // ASSUMPTION: Obtaining an instance cannot generate side-effects |
| return renode_connection_send_request(machine->renode->conn, get_instance_handler, instance_descriptor, |
| REQUEST_HEADER(api_command), |
| {instance_descriptor, sizeof(*instance_descriptor)}, |
| {&machine->md, sizeof(machine->md)}, |
| {&name_length, sizeof(name_length)}, |
| {name, name_length}, |
| ); |
| } |
| |
| renode_error_t *renode_run_for(renode_t *renode, renode_time_t time) |
| { |
| assert(renode != NULL); |
| |
| return renode_connection_send_request(renode->conn, generic_response_handler, NULL, |
| REQUEST_HEADER(RUN_FOR), |
| {&time, sizeof(time)}, |
| ); |
| } |
| |
| static renode_error_t *current_time_handler(renode_connection_t *conn, const void *data, size_t size, void *ud) |
| { |
| (void)conn; |
| |
| assert_fmsg(size == sizeof(uint64_t), "Expected %zu bytes, but received: %zu", sizeof(uint64_t), size); |
| memcpy(ud, data, sizeof(uint64_t)); |
| return NO_ERROR; |
| } |
| |
| renode_error_t *renode_get_current_time(renode_t *renode, renode_time_t *current_time) |
| { |
| assert(renode != NULL); |
| |
| return renode_connection_send_request(renode->conn, generic_response_handler, current_time, |
| REQUEST_HEADER(GET_TIME), |
| {current_time, sizeof(*current_time)}, |
| ); |
| } |
| |
| renode_error_t *renode_register_time_elapsed_callback(renode_t *renode, void *user_data, renode_time_elapsed_callback_t callback) |
| { |
| int32_t ed; |
| return_error_if_fails(register_callback((raw_callback_t)callback, user_data, &ed)); |
| |
| return renode_connection_send_request(renode->conn, generic_response_handler, NULL, |
| REQUEST_HEADER(TIME_ELAPSED_CALLBACK), |
| {&ed, sizeof(ed)}, |
| ); |
| } |
| |
| renode_error_t *renode_get_adc(renode_machine_t *machine, const char *name, renode_adc_t **adc) |
| { |
| int32_t id; |
| return_error_if_fails(renode_get_instance_descriptor(machine, ADC, name, &id)); |
| |
| *adc = xmalloc(sizeof(renode_adc_t)); |
| (*adc)->machine = machine; |
| (*adc)->id = id; |
| |
| return NO_ERROR; |
| } |
| |
| typedef enum { |
| GET_CHANNEL_COUNT = 0, |
| GET_CHANNEL_VALUE, |
| SET_CHANNEL_VALUE, |
| } adc_operation_t; |
| |
| typedef struct { |
| struct __attribute__((packed)) { |
| int32_t id; |
| uint8_t command; |
| } header; |
| |
| int32_t result_value; |
| } adc_command_t; |
| |
| static renode_error_t *adc_handler(renode_connection_t *conn, const void *data, size_t size, void *ud) |
| { |
| (void)conn; |
| |
| adc_command_t *cmd = ud; |
| switch (cmd->header.command) { |
| case GET_CHANNEL_COUNT: |
| case GET_CHANNEL_VALUE: |
| assert_fmsg(size == sizeof(int32_t), "Expected %zu bytes, but received: %zu", sizeof(int32_t), size); |
| memcpy(&cmd->result_value, data, sizeof(int32_t)); |
| return NO_ERROR; |
| case SET_CHANNEL_VALUE: |
| return NO_ERROR; // Nothing to do - we only care about a successfull status |
| default: |
| // TODO: Maybe assert_exit instead? (This is most likely a programmer error) |
| return create_fatal_error_static("Unknown ADC operation"); |
| } |
| } |
| |
| renode_error_t *renode_get_adc_channel_count(renode_adc_t *adc, int32_t *count) |
| { |
| adc_command_t cmd = { |
| .header = { |
| .id = adc->id, |
| .command = GET_CHANNEL_COUNT, |
| }, |
| }; |
| |
| return_error_if_fails(renode_connection_send_request(adc->machine->renode->conn, generic_response_handler, &cmd, |
| REQUEST_HEADER(ADC), |
| {&cmd, sizeof(cmd.header)}, |
| )); |
| |
| *count = cmd.result_value; |
| return NO_ERROR; |
| } |
| |
| renode_error_t *renode_get_adc_channel_value(renode_adc_t *adc, int32_t channel, uint32_t *value) |
| { |
| adc_command_t cmd = { |
| .header = { |
| .id = adc->id, |
| .command = GET_CHANNEL_VALUE, |
| }, |
| }; |
| |
| return_error_if_fails(renode_connection_send_request(adc->machine->renode->conn, generic_response_handler, &cmd, |
| REQUEST_HEADER(ADC), |
| {&cmd, sizeof(cmd.header)}, |
| {&channel, sizeof(channel)}, |
| )); |
| |
| *value = cmd.result_value; |
| return NO_ERROR; |
| } |
| |
| renode_error_t *renode_set_adc_channel_value(renode_adc_t *adc, int32_t channel, uint32_t value) |
| { |
| adc_command_t cmd = { |
| .header = { |
| .id = adc->id, |
| .command = SET_CHANNEL_VALUE, |
| }, |
| }; |
| |
| return renode_connection_send_request(adc->machine->renode->conn, generic_response_handler, &cmd, |
| REQUEST_HEADER(ADC), |
| {&cmd, sizeof(cmd.header)}, |
| {&channel, sizeof(channel)}, |
| {&value, sizeof(value)}, |
| ); |
| } |
| |
| renode_error_t *renode_get_gpio(renode_machine_t *machine, const char *name, renode_gpio_t **gpio) |
| { |
| int32_t id; |
| return_error_if_fails(renode_get_instance_descriptor(machine, GPIO, name, &id)); |
| |
| *gpio = xmalloc(sizeof(renode_gpio_t)); |
| (*gpio)->machine = machine; |
| (*gpio)->id = id; |
| |
| return NO_ERROR; |
| } |
| |
| typedef enum { |
| GET_STATE, |
| SET_STATE, |
| REGISTER_EVENT, |
| } gpio_operation_t; |
| |
| typedef struct { |
| struct __attribute__((packed)) { |
| int32_t id; |
| int8_t command; |
| int32_t number; |
| } header; |
| |
| uint8_t state; |
| } gpio_command_t; |
| |
| static renode_error_t *gpio_handler(renode_connection_t *conn, const void *data, size_t size, void *ud) |
| { |
| (void)conn; |
| |
| gpio_command_t *cmd = ud; |
| switch (cmd->header.command) { |
| case GET_STATE: |
| assert_fmsg(size == sizeof(uint8_t), "Expected %zu bytes, but received: %zu", sizeof(uint8_t), size); |
| memcpy(&cmd->state, data, sizeof(uint8_t)); |
| return NO_ERROR; |
| case SET_STATE: |
| case REGISTER_EVENT: |
| return NO_ERROR; // Nothing to do here - we only care about the successfull status |
| default: |
| // TODO: Maybe assert_exit instead? (This is most likely a programmer error) |
| return create_fatal_error_static("Unknown GPIO operation"); |
| } |
| } |
| |
| renode_error_t *renode_get_gpio_state(renode_gpio_t *gpio, int32_t id, bool *state) |
| { |
| gpio_command_t cmd = { |
| .header = { |
| .id = gpio->id, |
| .command = GET_STATE, |
| .number = id, |
| }, |
| }; |
| |
| return_error_if_fails(renode_connection_send_request(gpio->machine->renode->conn, generic_response_handler, &cmd, |
| REQUEST_HEADER(GPIO), |
| {&cmd.header, sizeof(cmd.header)}, |
| )); |
| |
| *state = cmd.state; |
| return NO_ERROR; |
| } |
| |
| renode_error_t *renode_set_gpio_state(renode_gpio_t *gpio, int32_t id, bool state) |
| { |
| gpio_command_t cmd = { |
| .header = { |
| .id = gpio->id, |
| .command = SET_STATE, |
| .number = id, |
| }, |
| .state = state, |
| }; |
| |
| return renode_connection_send_request(gpio->machine->renode->conn, generic_response_handler, &cmd, |
| REQUEST_HEADER(GPIO), |
| {&cmd.header, sizeof(cmd.header)}, |
| {&cmd.state, sizeof(cmd.state)}, |
| ); |
| } |
| |
| struct gpio_callback_data |
| { |
| bool current_state; |
| }; |
| |
| renode_error_t *renode_register_gpio_state_change_callback(renode_gpio_t *gpio, int32_t id, void *user_data, void (*callback)(void *, renode_gpio_event_data_t *)) |
| { |
| int32_t ed; |
| return_error_if_fails(register_callback((raw_callback_t)callback, user_data, &ed)); |
| |
| gpio_command_t cmd = { |
| .header = { |
| .id = gpio->id, |
| .command = REGISTER_EVENT, |
| .number = id, |
| }, |
| }; |
| |
| return renode_connection_send_request(gpio->machine->renode->conn, generic_response_handler, &cmd, |
| REQUEST_HEADER(GPIO), |
| {&cmd.header, sizeof(cmd.header)}, |
| {&ed, sizeof(ed)}, |
| ); |
| } |
| |
| typedef enum |
| { |
| SEND_FRAME = 0, |
| RECEIVED_FRAME = 1, |
| REGISTER_CALLBACKS = 2, |
| } canbus_operation_t; |
| |
| typedef struct { |
| struct __attribute__((packed)) { |
| int32_t id; |
| int8_t command; |
| } header; |
| int32_t packet_length; |
| uint32_t id; |
| void * packet; |
| } canbus_command_t; |
| |
| renode_error_t *renode_get_can(renode_machine_t *machine, const char *name, renode_can_t **can) |
| { |
| int32_t id; |
| return_error_if_fails(renode_get_instance_descriptor(machine, CAN_BUS, name, &id)); |
| |
| *can = xmalloc(sizeof(renode_can_t)); |
| (*can)->machine = machine; |
| (*can)->id = id; |
| |
| return NO_ERROR; |
| } |
| |
| renode_error_t *renode_send_can_message(renode_can_t *can, void *packet, int packet_length, uint32_t packet_id) |
| { |
| canbus_command_t cmd = { |
| .header = { |
| .id = can->id, |
| .command = SEND_FRAME, |
| }, |
| .packet_length = packet_length, |
| .id = packet_id, |
| }; |
| |
| return_error_if_fails(renode_connection_send_request(can->machine->renode->conn, generic_response_handler, &cmd, |
| REQUEST_HEADER(CAN_BUS), |
| {&cmd.header, sizeof(cmd.header)}, |
| {&cmd.packet_length, sizeof(cmd.packet_length)}, |
| {&cmd.id, sizeof(cmd.id)}, |
| {packet, packet_length}, |
| )); |
| |
| return NO_ERROR; |
| } |
| |
| renode_error_t *renode_register_can_callback(renode_can_t *can, void *user_data, void (*callback)(void *, renode_can_event_data_t *)) |
| { |
| int32_t ed; |
| return_error_if_fails(register_callback((raw_callback_t)callback, user_data, &ed)); |
| |
| canbus_command_t cmd = { |
| .header = { |
| .command = REGISTER_CALLBACKS, |
| }, |
| }; |
| |
| return renode_connection_send_request(can->machine->renode->conn, generic_response_handler, &cmd, |
| REQUEST_HEADER(CAN_BUS), |
| {&cmd.header, sizeof(cmd.header)}, |
| {&ed, sizeof(ed)}, |
| ); |
| } |
| |
| renode_error_t *renode_get_bus_context(renode_machine_t *machine, const char *name, renode_bus_context_t **ctx) |
| { |
| int32_t id; |
| return_error_if_fails(renode_get_instance_descriptor(machine, SYSTEM_BUS, name, &id)); |
| |
| *ctx = xmalloc(sizeof(renode_bus_context_t)); |
| (*ctx)->machine = machine; |
| (*ctx)->id = id; |
| |
| return NO_ERROR; |
| } |
| |
| renode_error_t *renode_get_sysbus(renode_machine_t *machine, renode_bus_context_t **sysbus) |
| { |
| return renode_get_bus_context(machine, "sysbus", sysbus); |
| } |
| |
| renode_error_t *renode_get_byte_count(renode_access_width_t width, uint32_t count, uint32_t *byte_count) |
| { |
| if (width == AW_MULTI_BYTE) { |
| *byte_count = count; |
| return NO_ERROR; |
| } |
| |
| uint32_t result; |
| switch (width) { |
| case AW_BYTE: |
| case AW_WORD: |
| case AW_DOUBLE_WORD: |
| case AW_QUAD_WORD: |
| result = (uint32_t)width * count; |
| // Handle overflow |
| if (result / (uint32_t)width != count) { |
| return create_error(ERR_INVALID_ARGUMENT, "Payload size exceeds %u bytes", UINT32_MAX); |
| } |
| *byte_count = result; |
| return NO_ERROR; |
| default: |
| return create_error(ERR_INVALID_ARGUMENT, "Invalid bus access width: %d", width); |
| } |
| } |
| |
| typedef enum { |
| SYSBUS_READ = 0, |
| SYSBUS_WRITE = 1, |
| SYSBUS_GET_NAME = 2, |
| } sysbus_operation_t; |
| |
| static renode_error_t *get_bus_context_name_handler(renode_connection_t *conn, const void *response, size_t response_size, void *ud) |
| { |
| (void)conn; |
| |
| message_payload_t header; |
| const void *data; |
| uint32_t data_size; |
| |
| return_error_if_fails(parse_response(response, response_size, &header, &data, &data_size)); |
| |
| if (header.command != SYSTEM_BUS) { |
| return create_fatal_error_static(ERRMSG_COMMAND_MISMATCH); |
| } |
| |
| char **name = ud; |
| *name = xmalloc(data_size + 1); |
| memcpy(*name, data, data_size); |
| (*name)[data_size] = '\0'; |
| return NO_ERROR; |
| } |
| |
| renode_error_t *renode_get_bus_context_name(renode_bus_context_t *ctx, char **name) |
| { |
| struct __attribute__((packed)) { |
| int32_t id; |
| uint8_t operation; |
| } command_data; |
| |
| command_data.id = ctx->id; |
| command_data.operation = SYSBUS_GET_NAME; |
| |
| // ASSUMPTION: Obtaining the name cannot generate side-effects |
| return renode_connection_send_request(ctx->machine->renode->conn, get_bus_context_name_handler, name, |
| REQUEST_HEADER(SYSTEM_BUS), |
| {&command_data, sizeof(command_data)}, |
| ); |
| } |
| |
| typedef struct { |
| struct __attribute__((packed)) { |
| int32_t id; |
| uint8_t operation; |
| uint8_t access_width; |
| uint64_t address; |
| uint32_t data_count; |
| } header; |
| |
| void *data; |
| uint32_t data_size; |
| } sysbus_command_t; |
| |
| static renode_error_t *sysbus_handler(renode_connection_t *conn, const void *data, size_t size, void *ud) |
| { |
| (void)conn; |
| |
| sysbus_command_t *cmd = ud; |
| switch (cmd->header.operation) { |
| case SYSBUS_READ: |
| assert_fmsg(size == cmd->data_size, "Expected to receive %zu bytes, but received %zu instead", cmd->data_size, size); |
| memcpy(cmd->data, data, size); |
| return NO_ERROR; |
| case SYSBUS_WRITE: |
| return NO_ERROR; |
| default: |
| // TODO: Maybe assert_exit instead? (This is most likely a programmer error) |
| return create_fatal_error_static("Unknown sysbus operation"); |
| } |
| } |
| |
| renode_error_t *renode_sysbus_read(renode_bus_context_t *ctx, uint64_t address, renode_access_width_t width, void *buffer, uint32_t count) |
| { |
| sysbus_command_t cmd = { |
| .header = { |
| .id = ctx->id, |
| .operation = SYSBUS_READ, |
| .access_width = width, |
| .address = address, |
| .data_count = count, |
| }, |
| .data = buffer, |
| }; |
| return_error_if_fails(renode_get_byte_count(width, count, &cmd.data_size)); |
| |
| return renode_connection_send_request(ctx->machine->renode->conn, generic_response_handler, &cmd, |
| REQUEST_HEADER(SYSTEM_BUS), |
| {&cmd.header, sizeof(cmd.header)}, |
| ); |
| } |
| |
| renode_error_t *renode_sysbus_write(renode_bus_context_t *ctx, uint64_t address, renode_access_width_t width, const void *buffer, uint32_t count) |
| { |
| sysbus_command_t cmd = { |
| .header = { |
| .id = ctx->id, |
| .operation = SYSBUS_WRITE, |
| .access_width = width, |
| .address = address, |
| .data_count = count, |
| }, |
| }; |
| |
| uint32_t data_bytes; |
| return_error_if_fails(renode_get_byte_count(width, count, &data_bytes)); |
| |
| return renode_connection_send_request(ctx->machine->renode->conn, generic_response_handler, &cmd, |
| REQUEST_HEADER(SYSTEM_BUS), |
| {&cmd.header, sizeof(cmd.header)}, |
| {buffer, data_bytes}, |
| ); |
| } |
| |
| static renode_error_t *parse_response(const void *response, size_t size, message_payload_t *header, const void **data, uint32_t *data_size) |
| { |
| assert_fmsg(size >= sizeof(*header), "Expected at least %zu command data header bytes, but got only %zu", sizeof(*header), size); |
| memcpy(header, response, sizeof(*header)); |
| |
| response += sizeof(*header); |
| size -= sizeof(*header); |
| |
| verbose_print("Incomming command: 0x%"PRIx16", message type: 0x%"PRIx8, header->command, header->type); |
| |
| switch(header->type) { |
| case TYPE_REQUEST: |
| case TYPE_SUCCESS: |
| case TYPE_EVENT_REQUEST: |
| *data = response; |
| *data_size = size; |
| return NO_ERROR; |
| case TYPE_INVALID_COMMAND: |
| return create_error_static(ERR_INVALID_COMMAND, "Unknown command"); |
| case TYPE_ERROR: { |
| uint8_t err_code = header->command == 0x0 ? ERR_FATAL : ERR_COMMAND_FAILED; |
| if (size > 0) { |
| char *message = xmalloc(size + 1); |
| memcpy(message, response, size); |
| message[size] = '\0'; |
| return create_error_dynamic(err_code, message); |
| } else { |
| return create_error_static(err_code, "No error message provided"); |
| } |
| } |
| default: |
| return create_fatal_error_static("Unexpected message type"); |
| } |
| } |
| |
| static renode_error_t *generic_request_handler(renode_connection_t *conn, uint16_t id, const void *response, size_t size, void *ud) |
| { |
| (void)ud; |
| |
| message_payload_t header; |
| const void *data = NULL; |
| uint32_t data_size = 0; |
| |
| return_error_if_fails(parse_response(response, size, &header, &data, &data_size)); |
| |
| assert_msg(header.type == TYPE_EVENT_REQUEST || header.type == TYPE_REQUEST, "Tried to invoke a request handler with a response"); |
| |
| if (header.type == TYPE_EVENT_REQUEST) { |
| int32_t ed; |
| assert_fmsg(sizeof(ed) <= data_size, "Expected at least %zu bytes of an event descriptor, but got only %zu", sizeof(ed), size); |
| memcpy(&ed, data, sizeof(ed)); |
| return invoke_callback(conn, id, header.command, ed, data + sizeof(ed), data_size - sizeof(ed)); |
| } |
| |
| return create_fatal_error_static("Current client implementation cannot service direct requests"); |
| } |
| |
| static renode_error_t *generic_response_handler(renode_connection_t *conn, const void *response, size_t size, void *ud) |
| { |
| message_payload_t header; |
| const void *data = NULL; |
| uint32_t data_size = 0; |
| |
| return_error_if_fails(parse_response(response, size, &header, &data, &data_size)); |
| |
| assert_msg(header.type != TYPE_EVENT_REQUEST && header.type != TYPE_REQUEST, "Tried to invoke a response handler with a request"); |
| |
| switch (header.command) { |
| case RUN_FOR: |
| return NO_ERROR; // This command doesn't return any data (The success status means that run_for has finished) |
| case GET_TIME: |
| return current_time_handler(conn, data, data_size, ud); |
| case GET_MACHINE: |
| return get_machine_handler(conn, data, data_size, ud); |
| case ADC: |
| return adc_handler(conn, data, data_size, ud); |
| case GPIO: |
| return gpio_handler(conn, data, data_size, ud); |
| case SYSTEM_BUS: |
| return sysbus_handler(conn, data, data_size, ud); |
| case TIME_ELAPSED_CALLBACK: |
| return NO_ERROR; // This command doesn't return any data, we only care about a successful status |
| case SPI: |
| return NO_ERROR; // This command doesn't return any data, we only care about a successful status |
| case CAN_BUS: |
| return NO_ERROR; // This command doesn't return any data, we only care about a successful status |
| default: |
| return create_fatal_error_static("Encountered a command without a response handler"); |
| } |
| } |
| |
| renode_error_t *renode_get_spi(renode_machine_t *machine, const char *name, renode_spi_t **spi) |
| { |
| int32_t id; |
| return_error_if_fails(renode_get_instance_descriptor(machine, SPI, name, &id)); |
| |
| *spi = xmalloc(sizeof(renode_spi_t)); |
| (*spi)->machine = machine; |
| (*spi)->id = id; |
| (*spi)->user_data = NULL; |
| (*spi)->on_transmit = NULL; |
| (*spi)->on_finish = NULL; |
| |
| return NO_ERROR; |
| } |
| |
| renode_error_t *renode_spi_register_callbacks(renode_spi_t *spi, void *user_data, |
| renode_spi_transmit_callback_t on_transmit, renode_spi_finish_callback_t on_finish) |
| { |
| spi->user_data = user_data; |
| spi->on_transmit = on_transmit; |
| spi->on_finish = on_finish; |
| |
| int32_t ed; |
| return_error_if_fails(register_callback(NULL, spi, &ed)); |
| |
| assert_msg(on_transmit != NULL, "spi on_transmit callback cannot be null"); |
| |
| return renode_connection_send_request(spi->machine->renode->conn, generic_response_handler, NULL, |
| REQUEST_HEADER(SPI), |
| {&spi->id, sizeof(spi->id)}, |
| {&ed, sizeof(ed)}, |
| ); |
| } |