void SIMLINK_config_remove_value(conf_object_t *link, const char *key);
Note that this function may delay the transmission if it is not possible to send the configuration message yet. The message will be buffered and send when possible. The ordering of configuration messages is kept when buffering them.
All link objects representing link in the simulation will
be called via the remove_config_value() function declared in
link_type_t
, including the one initiating the message.
static void ser_link_ep_pre_delete_instance(conf_object_t *ep) { char ep_id[19]; snprintf(ep_id, sizeof(ep_id), "ep%llx", SIMLINK_endpoint_id(ep)); SIMLINK_config_remove_value(SIMLINK_endpoint_link(ep), ep_id); SIMLINK_endpoint_disconnect(ep); }
void SIMLINK_config_update_value(conf_object_t *link, const char *key, const frags_t *value);
Note that this function may delay the transmission if it is not possible to send the configuration message yet. The message will be buffered and send when possible. The ordering of configuration messages is kept when buffering them.
All link objects representing link in the simulation will
be called via the update_config_value() function declared in
link_type_t
, including the one initiating the message.
static void snoop_ep_finalize_instance(conf_object_t *ep) { ep_finalize_instance(ep); /* Tell all endpoints that there's a new snoop in town. */ char ep_id[17]; snprintf(ep_id, sizeof(ep_id), "%llx", SIMLINK_endpoint_id(ep)); frags_t value; frags_init(&value); /* empty value, just to put the key in the database */ SIMLINK_config_update_value( SIMLINK_endpoint_link(ep), ep_id, &value); }
conf_object_t * SIMLINK_endpoint_clock(const conf_object_t *ep_obj);
NULL
if the device associated to an endpoint does not
have its queue attribute set. This indicates a configuration problem, as
the device would be unable to send or receive link messages.
const char * SIMLINK_endpoint_dev_name(const conf_object_t *ep_obj, buffer_t scratch);
This function is provided for logging purposes.
#define BUFFER_T(buf) (buffer_t){ .len = sizeof(buf), .data = buf } static void deliver_hub(conf_object_t *ep, const link_message_t *msgdata) { uint8 buf[1000]; SIM_LOG_INFO(3, ep, 0, "delivering to %s", SIMLINK_endpoint_dev_name(ep, BUFFER_T(buf)));
conf_object_t * SIMLINK_endpoint_device(const conf_object_t *ep_obj);
static void deliver(conf_object_t *ep, const link_message_t *lm) { const datagram_link_message_t *m = (const datagram_link_message_t *)lm; conf_object_t *dev = SIMLINK_endpoint_device(ep); const char *port = SIMLINK_endpoint_port(ep); const datagram_link_interface_t *dli = SIM_c_get_port_interface(dev, "datagram_link", port); if (dli) dli->receive(dev, m->payload); else SIM_LOG_ERROR(ep, 0, "Device does not implement" " datagram_link interface"); }
void SIMLINK_endpoint_disconnect(conf_object_t *ep_obj);
static void ser_link_ep_pre_delete_instance(conf_object_t *ep) { char ep_id[19]; snprintf(ep_id, sizeof(ep_id), "ep%llx", SIMLINK_endpoint_id(ep)); SIMLINK_config_remove_value(SIMLINK_endpoint_link(ep), ep_id); SIMLINK_endpoint_disconnect(ep); }
void SIMLINK_endpoint_finalize(conf_object_t *ep_obj);
static void ser_link_ep_finalize_instance(conf_object_t *ep) { SIMLINK_endpoint_finalize(ep); }
uint64 SIMLINK_endpoint_id(const conf_object_t *ep);
static void ser_link_ep_pre_delete_instance(conf_object_t *ep) { char ep_id[19]; snprintf(ep_id, sizeof(ep_id), "ep%llx", SIMLINK_endpoint_id(ep)); SIMLINK_config_remove_value(SIMLINK_endpoint_link(ep), ep_id); SIMLINK_endpoint_disconnect(ep); }
void SIMLINK_endpoint_init(conf_object_t *obj, bool snoop);
static void * datagram_link_endpoint_init_object(conf_object_t *obj, void *data) { datagram_link_endpoint_t *dlep = (datagram_link_endpoint_t *)obj; SIMLINK_endpoint_init(&dlep->obj, false); return dlep; }
bool SIMLINK_endpoint_is_device(const conf_object_t *ep);
true
if the endpoint is connected
to a device, false
otherwisestatic void switch_deliver_frame(conf_object_t *link, conf_object_t *ep, vlan_tag_t vlan_tag, uint64 src_epid, const frags_t *frame) { eth_frame_crc_status_t crc_status = Eth_Frame_CRC_Match; if (SIMLINK_endpoint_is_device(ep)) { switch_ep_t *swep = (switch_ep_t *)ep; if (frags_len(frame) > 12) { uint8 src_mac[6]; frags_extract_slice(frame, src_mac, 6, 6); learn(link, swep, vlan_tag, src_mac, src_epid); } swep->cep.ifc->frame(SIMLINK_endpoint_device(ep), frame, crc_status); } else { snoop_ep_t *snoop = (snoop_ep_t *)ep; deliver_to_snoop(snoop->snoop_fun, snoop->user_data, SIMLINK_endpoint_clock(ep), frame, crc_status); } }
conf_object_t * SIMLINK_endpoint_link(const conf_object_t *ep);
static void deliver(conf_object_t *ep, const link_message_t *msgd) { ser_link_endpoint_t *slep = (ser_link_endpoint_t *)ep; ser_link_impl_t *slink = (ser_link_impl_t *)SIMLINK_endpoint_link(ep); ser_link_message_t *msg = (ser_link_message_t *)msgd; switch (msg->msgtype) { case MSG_Char:
const char * SIMLINK_endpoint_port(const conf_object_t *ep_obj);
The port returned might be NULL
, which means that the
device is implementing a classic interface rather than a port interface.
NULL
if no port is usedstatic void ser_link_ep_finalize_instance(conf_object_t *ep) { SIMLINK_endpoint_finalize(ep); }
void SIMLINK_finalize(conf_object_t *obj);
static void datagram_link_finalize_instance(conf_object_t *obj) { SIMLINK_finalize(obj); }
conf_object_t * SIMLINK_find_endpoint_by_id(conf_object_t *link, uint64 id);
NULL
otherwise.NULL
if not
found
void SIMLINK_init(conf_object_t *obj, const link_type_t *type);
link_type_t
type argument. This function is
intended to be called in the init_object() method of a link.static const link_type_t ser_link_type = { .msg_to_attr = msg_to_attr, .msg_from_attr = msg_from_attr, .free_msg = free_message, .marshal = marshal, .unmarshal = unmarshal, .deliver = deliver, .update_config_value = link_config_value_updated, .remove_config_value = link_config_value_removed, .device_changed = ser_link_ep_device_changed }; static conf_object_t * ser_link_alloc_object(void *arg) { ser_link_impl_t *slink = MM_ZALLOC(1, ser_link_impl_t); return &slink->obj; } static void * ser_link_init_object(conf_object_t *obj, void *arg) { ser_link_impl_t *slink = (ser_link_impl_t *)obj; SIMLINK_init(&slink->obj, &ser_link_type); slink->buffer_size = 10; /* a reasonable default value? */ return obj; }
void SIMLINK_init_library(void);
void init_local(void) { /* The link library must always be initialised first. */ SIMLINK_init_library();
void SIMLINK_init_message(link_message_t *msg);
static link_message_t * new_datagram_message(const uint8 *data, size_t len) { datagram_link_message_t *m = MM_MALLOC(1, datagram_link_message_t); SIMLINK_init_message(&m->common); uint8 *d = MM_MALLOC(len, uint8); memcpy(d, data, len); m->payload = (bytes_t){.data = d, .len = len}; return &m->common; }
void SIMLINK_pre_delete(conf_object_t *obj);
static void ser_link_pre_delete_instance(conf_object_t *obj) { SIMLINK_pre_delete(obj); }
void SIMLINK_register_class(conf_class_t *cls);
void init_local(void) { SIMLINK_init_library(); const class_data_t link_cls_funcs = { .alloc_object = ser_link_alloc_object, .init_object = ser_link_init_object, .finalize_instance = ser_link_finalize_instance, .pre_delete_instance = ser_link_pre_delete_instance, .delete_instance = ser_link_delete_instance, .class_desc = "model of serial link", .description = "Serial link" }; conf_class_t *link_cls = SIM_register_class("ser-link-impl", &link_cls_funcs); SIMLINK_register_class(link_cls); SIM_register_typed_attribute( link_cls, "buffer_size", get_link_buffer_size, NULL, set_link_buffer_size, NULL, Sim_Attr_Optional, "i", NULL, "The number of characters that the link may buffer. Must" " be at least one."); const class_data_t ep_cls_funcs = { .alloc_object = ser_link_ep_alloc_object, .init_object = ser_link_ep_init_object, .finalize_instance = ser_link_ep_finalize_instance, .pre_delete_instance = ser_link_ep_pre_delete_instance, .delete_instance = ser_link_ep_delete_instance, .class_desc = "serial link endpoint", .description = "Serial link endpoint" }; conf_class_t *ep_cls = SIM_register_class("ser-link-endpoint", &ep_cls_funcs); SIMLINK_register_endpoint_class(ep_cls, "[s]|[si]");
void SIMLINK_register_endpoint_class(conf_class_t *cls, const char *msg_type);
msg_type is a string defining the type of the attribute
representing a link message, as returned by msg_to_attr() in
link_type_t
.
void init_local(void) { SIMLINK_init_library(); const class_data_t link_cls_funcs = { .alloc_object = ser_link_alloc_object, .init_object = ser_link_init_object, .finalize_instance = ser_link_finalize_instance, .pre_delete_instance = ser_link_pre_delete_instance, .delete_instance = ser_link_delete_instance, .class_desc = "model of serial link", .description = "Serial link" }; conf_class_t *link_cls = SIM_register_class("ser-link-impl", &link_cls_funcs); SIMLINK_register_class(link_cls); SIM_register_typed_attribute( link_cls, "buffer_size", get_link_buffer_size, NULL, set_link_buffer_size, NULL, Sim_Attr_Optional, "i", NULL, "The number of characters that the link may buffer. Must" " be at least one."); const class_data_t ep_cls_funcs = { .alloc_object = ser_link_ep_alloc_object, .init_object = ser_link_ep_init_object, .finalize_instance = ser_link_ep_finalize_instance, .pre_delete_instance = ser_link_ep_pre_delete_instance, .delete_instance = ser_link_ep_delete_instance, .class_desc = "serial link endpoint", .description = "Serial link endpoint" }; conf_class_t *ep_cls = SIM_register_class("ser-link-endpoint", &ep_cls_funcs); SIMLINK_register_endpoint_class(ep_cls, "[s]|[si]");
void SIMLINK_register_snoop_endpoint_class(conf_class_t *cls);
void init_local(void) { SIMLINK_init_library(); init_eth_hub_link(); init_eth_cable_link(); init_eth_switch_link(); init_ethernet_crc_table(); const class_data_t snoop_ep_cls_funcs = { .alloc_object = snoop_ep_alloc_object, .init_object = snoop_ep_init_object, .finalize_instance = ep_finalize_instance, .pre_delete_instance = snoop_ep_pre_delete_instance, .delete_instance = snoop_ep_delete_instance, .description = "Ethernet link snoop endpoint", .class_desc = "an Ethernet link snoop endpoint", .kind = Sim_Class_Kind_Pseudo, }; snoop_ep_cls = SIM_register_class("eth-link-snoop-endpoint", &snoop_ep_cls_funcs); SIMLINK_register_snoop_endpoint_class(snoop_ep_cls); }
void SIMLINK_send_message(conf_object_t *src_ep_obj, uint64 dst_id, link_message_t *msg);
LINK_BROADCAST_ID
, which will send the message to all
endpoints on the link except the sender.
It is important to note that the ownership of the message msg is passed to the link library when calling SIMLINK_send_message(). When returning, msg may have been already deallocated and should not be used anymore.
static void receive(conf_object_t *NOTNULL ep, bytes_t msg) { SIMLINK_send_message(ep, LINK_BROADCAST_ID, new_datagram_message(msg.data, msg.len)); }
void SIMLINK_send_message_multi(conf_object_t *src_ep_obj, unsigned num_dsts, const uint64 *dst_ids, link_message_t *msg);
LINK_BROADCAST_ID
.
It is important to note that the ownership of the message msg is passed to the link library when calling SIMLINK_send_message_multi(). When returning, msg may have been already deallocated and should not be used anymore.
static void send_message(signal_link_endpoint_t *slep, link_message_t *msg) { signal_link_t *slink = (signal_link_t *)SIMLINK_endpoint_link(&slep->obj); int num_dsts = ht_num_entries_int(&slink->receivers); uint64 dst_ids[num_dsts]; memset(dst_ids, 0, num_dsts * sizeof(uint64)); int i = 0; HT_FOREACH_INT(&slink->receivers, it) dst_ids[i++] = ht_iter_int_key(it); SIMLINK_send_message_multi(&slep->obj, num_dsts, dst_ids, msg); }
conf_object_t * SIMLINK_snoop_endpoint_create(conf_class_t *cls, conf_object_t *link, conf_object_t *clock, attr_value_t attrs);
snoop_attach
interface,
where endpoints can not be created using components as it is usually done.
SIMLINK_snoop_endpoint_create() takes as arguments the class of
the snoop endpoint object cls, the link object
link, and a list of attributes to set, in the same form as
provided to SIM_create_object().static conf_object_t * default_attach_snoop(conf_object_t *obj, conf_object_t *clock, ethernet_link_snoop_t snoop_fun, lang_void *user_data) { common_link_t *clink = (common_link_t *)obj; attach_snoop_helper(clink, clock); attr_value_t attrs = SIM_make_attr_list(0); snoop_ep_t *snoop = (snoop_ep_t *)SIMLINK_snoop_endpoint_create( snoop_ep_cls, &clink->obj, clock, attrs); SIM_attr_free(&attrs); snoop->snoop_fun = snoop_fun; snoop->user_data = user_data; return &snoop->cep.obj; }
typedef struct { link_message_t common; /* should always be first */ /* The actual data in the message - in our case an allocated byte string owned by this structure. */ bytes_t payload; } datagram_link_message_t;
typedef struct { attr_value_t (*msg_to_attr)(conf_object_t *link, const link_message_t *msg); link_message_t *(*msg_from_attr)(conf_object_t *link, attr_value_t attr); void (*free_msg)(conf_object_t *link, link_message_t *msg); void (*marshal)(conf_object_t *link, const link_message_t *msg, void (*finish)(void *data, const frags_t *msg), void *finish_data); link_message_t *(*unmarshal)(conf_object_t *link, const frags_t *msg); void (*deliver)(conf_object_t *ep, const link_message_t *msg); void (*update_config_value)(conf_object_t *link, const char *key, const frags_t *value); void (*remove_config_value)(conf_object_t *link, const char *key); void (*device_changed)(conf_object_t *ep, conf_object_t *old_dev); } link_type_t;
These functions can be classified in four groups:
All five functions can be called in any execution context and should be thread-safe. They all take the link object as argument, in case it contains information necessary to perform the operation. As the link object is shared between the cells in which it is connected, it should not be modified during execution. Mutable state should be kept in the endpoint objects instead.
msg_to_attr() transforms the message msg
into an attr_value_t
value. It is used to checkpoint
in-flight messages waiting to be delivered. The value returned will
be passed unchanged as argument attr to
msg_from_attr() when loading a checkpoint with pending
link messages. Neither function is expected to return an error,
although msg_from_attr() is allowed to return NULL when
translating a message it does not care to restore. This can be useful
to keep checkpoint compatibility with older versions of the same link
that do not always have the same message protocol.
Using the datagram_link as an example, the datagram-link message is defined as:
typedef struct { link_message_t common; /* should always be first */ /* The actual data in the message - in our case an allocated byte string owned by this structure. */ bytes_t payload; } datagram_link_message_t;
msg_to_attr() and msg_from_attr() are thus defined as:
static attr_value_t msg_to_attr(conf_object_t *link, const link_message_t *lm) { const datagram_link_message_t *m = (const datagram_link_message_t *)lm; return SIM_make_attr_data(m->payload.len, m->payload.data); }
static link_message_t * msg_from_attr(conf_object_t *link, attr_value_t attr) { return new_datagram_message(SIM_attr_data(attr), SIM_attr_data_size(attr)); }
free_msg() is called when the message msg has been delivered to all its destinations and is no longer needed. All memory allocated for msg is expected to be freed, including msg itself. The datagram_link defines free_msg() as:
static void free_msg(conf_object_t *link, link_message_t *lm) { datagram_link_message_t *m = (datagram_link_message_t *)lm; MM_FREE((uint8 *)m->payload.data); m->payload.data = NULL; MM_FREE(m); }
marshal() is called when the message msg
should be transmitted over a distributed simulation. Its purpose is
to serialize the message into a frags_t
representation. Rather than returning the marshaled message,
marshal() takes the finish and
finish_data arguments, that it is expected to call
once the message has been marshaled.
The reason behind this mechanism is that it allows
marshal() to perform its operations with a
frags_t
variable allocated on the stack, and thus to
skip any heap allocation when sending the message. In case memory was
allocated anyway, it should be freed just after finish
has returned.
static void marshal(conf_object_t *link, const link_message_t *lm, void (*finish)(void *data, const frags_t *msg), void *finish_data) { const datagram_link_message_t *m = (const datagram_link_message_t *)lm; /* Our message just consists of a byte string, so this is very easy. */ frags_t buf; frags_init_add(&buf, m->payload.data, m->payload.len); finish(finish_data, &buf); }
unmarshal() does the opposite of marshal(): it
takes a serialized frags_t
representation of the message
called data and returns a newly allocated link
message.
static link_message_t * unmarshal(conf_object_t *link, const frags_t *data) { size_t len = frags_len(data); uint8 bytes[len]; frags_extract(data, bytes); return new_datagram_message(bytes, len); }
In all of these cases, the device_changed() callback will be called when the endpoint's device attribute is changed and the endpoint has reached to finalize phase. In that callback, the new device can be obtained via SIMLINK_endpoint_device() and additional operations, such as interface caching, can be safely performed. The old device the endpoint was connected to is provided for convenience as an argument to device_changed().
Note that if no device related operations are necessary, this callback may be left unimplemented.
The ser_link implementation of device_changed is the following:
static void ser_link_ep_device_changed(conf_object_t *ep, conf_object_t *old_dev) { ser_link_endpoint_t *slep = (ser_link_endpoint_t *)ep; slep->serial_ifc = SIM_c_get_port_interface( SIMLINK_endpoint_device(ep), SERIAL_DEVICE_INTERFACE, SIMLINK_endpoint_port(ep)); if (!old_dev) { char ep_id[19]; snprintf(ep_id, sizeof(ep_id), "ep%llx", SIMLINK_endpoint_id(ep)); frags_t value; frags_init(&value); SIMLINK_config_update_value(SIMLINK_endpoint_link(ep), ep_id, &value); } }
Note that deliver() can be called in any execution context and should be thread-safe. The link object is shared between the cells in which it is connected, and should not be modified during execution. Mutable state should be kept in the endpoint objects instead.
The datagram_link implementation of deliver() is the following:
static void deliver(conf_object_t *ep, const link_message_t *lm) { const datagram_link_message_t *m = (const datagram_link_message_t *)lm; conf_object_t *dev = SIMLINK_endpoint_device(ep); const char *port = SIMLINK_endpoint_port(ep); const datagram_link_interface_t *dli = SIM_c_get_port_interface(dev, "datagram_link", port); if (dli) dli->receive(dev, m->payload); else SIM_LOG_ERROR(ep, 0, "Device does not implement" " datagram_link interface"); }
link_type_t
are taking
care of the link configuration itself. In the same way messages needs
to be marshaled when sent over a network, the global link
configuration needs to be agreed upon when running the simulation in
several processes.
update_config_value() is called whenever a configuration
parameter has been added or updated. The configuration parameter's
name is provided as key and its new value as
value, encoded as a frags_t
.
remove_config_value() is called whenever the configuration value key has been removed.
The interpretation of the link configuration messages is link specific. The only configuration parameter that is defined by the link library itself is goal_latency. This is handled entirely internally, although with the same mechanism as exposed here. Configuration changes are initiated by the link objects themselves with the Link Library API functions SIMLINK_config_update_value() and SIMLINK_config_remove_value().
Note that the link object that initiates the configuration change is also called back via update_config_value() and remove_config_value(). Note also that the configuration changes may be buffered and sent later if they are initiated too soon for the configuration message to propagate.
Configuration changes should only be initiated while in Global Context, so the two configuration functions above will only be called in Global Context. This allows them to modify properties of the link object itself without needing to care about thread safety.
As an example, here is how ser_link defines these two functions. The serial link keeps track of all endpoints connected to it by saving their ID as a configuration parameter. It also uses a configurable buffer size.
Finally, it is important to note that these two callbacks may be called from a non-execution thread. They should call the Simics API only via SIM_thread_safe_callback(). This includes calling the SIM_log_* functions.
static void link_config_value_updated(conf_object_t *link, const char *key, const frags_t *msg) { ser_link_impl_t *slink = (ser_link_impl_t *)link; if (strncmp(key, "ep", 2) == 0) { uint64 ep_id = strtoull(key + 2, NULL, 16); SIM_LOG_INFO(4, &slink->obj, 0, "Add endpoint: 0x%llx", ep_id); ht_update_int(&slink->endpoints, ep_id, NULL); } else if (strcmp(key, "buffer_size") == 0) { slink->buffer_size = frags_extract_be32(msg, 0); } else { ASSERT(false); } }
static void link_config_value_removed(conf_object_t *link, const char *key) { ser_link_impl_t *slink = (ser_link_impl_t *)link; if (strncmp(key, "ep", 2) == 0) { uint64 ep_id = strtoull(key + 2, NULL, 16); SIM_LOG_INFO(4, &slink->obj, 0, "Remove endpoint: 0x%llx", ep_id); ht_remove_int(&slink->endpoints, ep_id); } else { ASSERT(false); } }