SIMLINK_config_remove_value — remove a link configuration parameter
#include <simics/devs/liblink.h> // in C
import "simics/devs/liblink.dml"; // in DML
void
SIMLINK_config_remove_value(conf_object_t *link, const char *key);
Make sure that all link objects representing link in the simulation receive a configuration message to remove the configuration parameter 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.
None
Global Context
Example from ser-link-impl, where the ID of the endpoints present on the link is kept as a configuration parameter:
static void
ser_link_ep_deinit(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);
}
SIMLINK_config_update_value — update a link configuration parameter
#include <simics/devs/liblink.h> // in C
import "simics/devs/liblink.dml"; // in DML
void
SIMLINK_config_update_value(conf_object_t *link, const char *key,
const frags_t *value);
Make sure that all link objects representing link in the simulation will receive a configuration message for the new value of the configuration parameter key. Both key and value are completely link-specific and transported as-is to all objects.
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.
None
Global Context
Example from eth-switch-link, where the ID of all snooper endpoints present on the link are sent as a configuration parameter so they are included even when a packet can be sent directly to the receiver.
static void
snoop_ep_finalize(conf_object_t *ep)
{
ep_finalize(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);
}
SIMLINK_endpoint_clock — return endpoint’s clock
#include <simics/devs/liblink.h> // in C
import "simics/devs/liblink.dml"; // in DML
conf_object_t *
SIMLINK_endpoint_clock(const conf_object_t *ep_obj);
Return the endpoint ep’s associated clock object. It will be either the clock object corresponding to the device connected to the endpoint, or the clock chosen when using the endpoint for snooping.
The associated clock object. This function might return 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.
Cell Context
SIMLINK_endpoint_dev_name — return the name of the device or snooper to which an endpoint is connected
#include <simics/devs/liblink.h> // in C
import "simics/devs/liblink.dml"; // in DML
const char *
SIMLINK_endpoint_dev_name(const conf_object_t *ep_obj, buffer_t scratch);
Return the name of the device or snooper to which the endpoint ep is connected. This function takes an additional scratch parameter that is meant to provide space for putting together the answer when necessary, without allocating any memory. If scratch is used by SIMLINK_endpoint_dev_name() but is not long enough, the name will be truncated.
This function is provided for logging purposes.
The name of the device or snooper the endpoint is connected to
Cell Context
Example from eth-hub-link, to print the name of the device or snooper to which a frame is delivered:
#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)));
SIMLINK_endpoint_device — return the device to which an endpoint is connected
#include <simics/devs/liblink.h> // in C
import "simics/devs/liblink.dml"; // in DML
conf_object_t *
SIMLINK_endpoint_device(const conf_object_t *ep_obj);
Return the device to which the endpoint ep is connected. If the endpoint is not connected to a device, this function will trigger an assertion failure. This can be checked with SIMLINK_endpoint_is_device().
The device to which the endpoint is connected
Cell Context
Example from datagram_link_impl:
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");
}
SIMLINK_endpoint_disconnect — disconnect an endpoint object
#include <simics/devs/liblink.h> // in C
import "simics/devs/liblink.dml"; // in DML
void
SIMLINK_endpoint_disconnect(conf_object_t *ep_obj);
Disconnect the endpoint object ep_obj from its link. This function is intended to be called in the pre_delete_instance() method of an endpoint class. It should never be called in other circumstances, as endpoint objects should not be reused. Note that once the endpoint has been disconnected, it cannot be used for calls to Link Library API functions that takes an endpoint as argument.
None
Global Context
Example from ser-link-impl:
static void
ser_link_ep_deinit(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);
}
SIMLINK_endpoint_finalize — finalize an endpoint object
#include <simics/devs/liblink.h> // in C
import "simics/devs/liblink.dml"; // in DML
void
SIMLINK_endpoint_finalize(conf_object_t *ep_obj);
Finalize the endpoint object ep_obj. This function is intended to be called in the finalize_instance() method of an endpoint class.
None
Global Context
Example from ser-link-impl:
static void
ser_link_ep_finalize(conf_object_t *ep)
{
SIMLINK_endpoint_finalize(ep);
}
SIMLINK_endpoint_id — return endpoint’s ID
#include <simics/devs/liblink.h> // in C
import "simics/devs/liblink.dml"; // in DML
uint64
SIMLINK_endpoint_id(const conf_object_t *ep);
Return the endpoint ep’s ID.
The endpoint ID
Cell Context
In ser-link-impl, the endpoints IDs are kept as configuration values so they are known in all link objects:
static void
ser_link_ep_deinit(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);
}
SIMLINK_endpoint_init — initialize an endpoint object
#include <simics/devs/liblink.h> // in C
import "simics/devs/liblink.dml"; // in DML
void
SIMLINK_endpoint_init(conf_object_t *obj, bool snoop);
Initialize the endpoint object obj. Whether the endpoint is connected to a device or a snooper function is determined by the snoop parameter. This function is intended to be called in the init_object() method of an endpoint class.
None
Global Context
Example from datagram_link_impl:
static void *
datagram_link_endpoint_init(conf_object_t *obj)
{
datagram_link_endpoint_t *dlep =
(datagram_link_endpoint_t *)obj;
SIMLINK_endpoint_init(&dlep->obj, false);
return dlep;
}
SIMLINK_register_endpoint_class, SIMLINK_register_snoop_endpoint_class, SIMLINK_endpoint_finalize
SIMLINK_endpoint_is_device — return whether an endpoint is connected to a device
#include <simics/devs/liblink.h> // in C
import "simics/devs/liblink.dml"; // in DML
bool
SIMLINK_endpoint_is_device(const conf_object_t *ep);
Return whether the endpoint ep is connected to a device (as opposed to a link snooper).
true if the endpoint is connected to a device, false otherwise
Cell Context
Example from eth-switch-link, where frames are delivered either to a device endpoint or to a snoop endpoint, using different methods:
static 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);
}
}
SIMLINK_endpoint_link — return endpoint’s link
#include <simics/devs/liblink.h> // in C
import "simics/devs/liblink.dml"; // in DML
conf_object_t *
SIMLINK_endpoint_link(const conf_object_t *ep);
Return the link object to which the endpoint ep is connected.
The link object
Cell Context
In ser-link-impl, an endpoint needs to check the maximum size of its buffer by querying its link object:
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) {
SIMLINK_endpoint_port — return the device’s port to which an endpoint is connected
#include <simics/devs/liblink.h> // in C
import "simics/devs/liblink.dml"; // in DML
const char *
SIMLINK_endpoint_port(const conf_object_t *ep_obj);
Return the device’s port to which the endpoint ep is connected. If the endpoint is not connected to a device, this function will trigger an assertion failure. This can be checked with SIMLINK_endpoint_is_device().
The port returned might be NULL, which means that the device is implementing a classic interface rather than a port interface.
The device’s port to which the endpoint is connected, or NULL if no port is used
Cell Context
Example from ser-link-impl, where a pointer to the interface to call for delivery is kept in the endpoint structure:
static void
ser_link_ep_finalize(conf_object_t *ep)
{
SIMLINK_endpoint_finalize(ep);
}
SIMLINK_finalize — finalize a link object
#include <simics/devs/liblink.h> // in C
import "simics/devs/liblink.dml"; // in DML
void
SIMLINK_finalize(conf_object_t *obj);
Finalize the link object obj. This function is intended to be called in the finalize_instance() method of a link class.
None
Global Context
Example from ser-link-impl:
static void
datagram_link_finalize(conf_object_t *obj)
{
SIMLINK_finalize(obj);
}
SIMLINK_find_endpoint_by_id — return an endpoint object given its ID
#include <simics/devs/liblink.h> // in C
import "simics/devs/liblink.dml"; // in DML
conf_object_t *
SIMLINK_find_endpoint_by_id(conf_object_t *link, uint64 id);
Return the endpoint object with the ID id if the endpoint is connected to the link link, or NULL otherwise.
Endpoint object, or NULL if not found
Cell Context
SIMLINK_init — initialize a link object
#include <simics/devs/liblink.h> // in C
import "simics/devs/liblink.dml"; // in DML
void
SIMLINK_init(conf_object_t *obj, const link_type_t *type);
Initialize the link object obj. The link specific functions that will be called from the link library are gathered in the link_type_t type argument. This function is intended to be called in the init_object() method of a link.
None
Global Context
Example from ser-link-impl:
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(conf_class_t *cls)
{
ser_link_impl_t *slink = MM_ZALLOC(1, ser_link_impl_t);
return &slink->obj;
}
static void *
ser_link_init(conf_object_t *obj)
{
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? */
slink->connection_warning = false;
return obj;
}
SIMLINK_init_library — initialize the link library
#include <simics/devs/liblink.h> // in C
import "simics/devs/liblink.dml"; // in DML
void
SIMLINK_init_library();
Initialize the link library. This function is meant to be called in the init_local() function of a module linked to the library.
None
Global Context
Example from datagram_link_impl:
void
init_local()
{
// The link library must always be initialised first.
SIMLINK_init_library();
SIMLINK_init_message — initialize a link message
#include <simics/devs/liblink.h> // in C
import "simics/devs/liblink.dml"; // in DML
void
SIMLINK_init_message(link_message_t *msg);
Initialize the generic part of a link message.
None
Cell Context
Example from datagram_link_impl:
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;
}
SIMLINK_pre_delete — clean-up before link deletion
#include <simics/devs/liblink.h> // in C
import "simics/devs/liblink.dml"; // in DML
void
SIMLINK_pre_delete(conf_object_t *obj);
Performs clean-up operations before a link object can be safely deleted. This function is intended to be called in the pre_delete_instance() method of a link class.
None
Global Context
Example from ser-link-impl:
static void
ser_link_deinit(conf_object_t *obj)
{
SIMLINK_pre_delete(obj);
}
SIMLINK_register_class — register a link class
#include <simics/devs/liblink.h> // in C
import "simics/devs/liblink.dml"; // in DML
void
SIMLINK_register_class(conf_class_t *cls);
Complete the class cls with the necessary attributes and interfaces to be a usable link class. This function is meant to be called after cls has been obtained from SIM_register_class.
None
Global Context
Example from ser-link-impl:
void
init_local()
{
SIMLINK_init_library();
const class_info_t link_cls_funcs = {
.alloc = ser_link_alloc,
.init = ser_link_init,
.finalize = ser_link_finalize,
.deinit = ser_link_deinit,
.dealloc = ser_link_dealloc,
.short_desc = "model of serial link",
.description = "Serial link"
};
conf_class_t *link_cls = SIM_create_class("ser-link-impl",
&link_cls_funcs);
SIMLINK_register_class(link_cls);
SIM_register_attribute(
link_cls, "buffer_size", get_link_buffer_size,
set_link_buffer_size, Sim_Attr_Optional, "i",
"The number of characters that the link may buffer. Must"
" be at least one.");
const class_info_t ep_cls_funcs = {
.alloc = ser_link_ep_alloc,
.init = ser_link_ep_init,
.finalize = ser_link_ep_finalize,
.deinit = ser_link_ep_deinit,
.dealloc = ser_link_ep_dealloc,
.short_desc = "serial link endpoint",
.description = "Serial link endpoint"
};
conf_class_t *ep_cls = SIM_create_class("ser-link-endpoint",
&ep_cls_funcs);
SIMLINK_register_endpoint_class(ep_cls, "[s]|[si]");
SIMLINK_register_endpoint_class — register a link endpoint class
#include <simics/devs/liblink.h> // in C
import "simics/devs/liblink.dml"; // in DML
void
SIMLINK_register_endpoint_class(conf_class_t *cls, const char *msg_type);
Complete the class cls with the necessary attributes and interfaces to be a usable link endpoint class. This function is meant to be called after cls has been obtained from SIM_register_class.
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.
None
Global Context
Example from ser-link-impl:
void
init_local()
{
SIMLINK_init_library();
const class_info_t link_cls_funcs = {
.alloc = ser_link_alloc,
.init = ser_link_init,
.finalize = ser_link_finalize,
.deinit = ser_link_deinit,
.dealloc = ser_link_dealloc,
.short_desc = "model of serial link",
.description = "Serial link"
};
conf_class_t *link_cls = SIM_create_class("ser-link-impl",
&link_cls_funcs);
SIMLINK_register_class(link_cls);
SIM_register_attribute(
link_cls, "buffer_size", get_link_buffer_size,
set_link_buffer_size, Sim_Attr_Optional, "i",
"The number of characters that the link may buffer. Must"
" be at least one.");
const class_info_t ep_cls_funcs = {
.alloc = ser_link_ep_alloc,
.init = ser_link_ep_init,
.finalize = ser_link_ep_finalize,
.deinit = ser_link_ep_deinit,
.dealloc = ser_link_ep_dealloc,
.short_desc = "serial link endpoint",
.description = "Serial link endpoint"
};
conf_class_t *ep_cls = SIM_create_class("ser-link-endpoint",
&ep_cls_funcs);
SIMLINK_register_endpoint_class(ep_cls, "[s]|[si]");
SIMLINK_register_snoop_endpoint_class — register a link snoop endpoint class
#include <simics/devs/liblink.h> // in C
import "simics/devs/liblink.dml"; // in DML
void
SIMLINK_register_snoop_endpoint_class(conf_class_t *cls);
Complete the class cls with the necessary attributes and interfaces to be a usable link snoop endpoint class. This function is meant to be called after cls has been obtained from SIM_register_class().
None
Global Context
Example from the new Ethernet links:
void
init_local()
{
SIMLINK_init_library();
init_eth_hub_link();
init_eth_cable_link();
init_eth_switch_link();
init_ethernet_crc_table();
const class_info_t snoop_ep_cls_funcs = {
.alloc = snoop_ep_alloc,
.init = snoop_ep_init,
.finalize = ep_finalize,
.deinit = snoop_ep_deinit,
.dealloc = snoop_ep_dealloc,
.description = "Ethernet link snoop endpoint",
.short_desc = "an Ethernet link snoop endpoint",
.kind = Sim_Class_Kind_Pseudo,
};
snoop_ep_cls = SIM_create_class("eth-link-snoop-endpoint",
&snoop_ep_cls_funcs);
SIMLINK_register_snoop_endpoint_class(snoop_ep_cls);
}
SIMLINK_send_message — send a link message
#include <simics/devs/liblink.h> // in C
import "simics/devs/liblink.dml"; // in DML
void
SIMLINK_send_message(conf_object_t *src_ep_obj,
uint64 dst_id, link_message_t *msg);
Send a message msg from the endpoint src_ep_obj to the destination ID dst_id. The destination may be any valid endpoint ID on the link or 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.
None
Cell Context
Example from datagram_link_impl:
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));
}
SIMLINK_send_message_multi — send a link message to multiple recipients
#include <simics/devs/liblink.h> // in C
import "simics/devs/liblink.dml"; // in DML
void
SIMLINK_send_message_multi(conf_object_t *src_ep_obj, unsigned num_dsts,
const uint64 *dst_ids, link_message_t *msg);
Send a message msg from the endpoint src_ep_obj to the destinations IDs dst_ids. The length of the dst_ids list is provided by num_dsts. Each destination should be a valid endpoint ID on the link. It is not allowed to be 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.
None
Cell Context
Example from signal_link_impl, which keeps a list of endpoints to send to and specifically directs its messages to the appropriate endpoints:
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);
}
SIMLINK_snoop_endpoint_create — create a snoop endpoint object
#include <simics/devs/liblink.h> // in C
import "simics/devs/liblink.dml"; // in DML
conf_object_t *
SIMLINK_snoop_endpoint_create(conf_class_t *cls, conf_object_t *link,
conf_object_t *clock,
attr_value_t attrs);
This method returns an already created snoop endpoint object. It is meant to be used when implementing a ethernet_snoop or ethernet_vlan_snoop 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().
A snoop endpoint object
Global Context
Example from the new Ethernet links:
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;
}
link_message_t — generic part of a link message
#include <simics/devs/liblink.h> // in C
import "simics/devs/liblink.dml"; // in DML
typedef struct {
atomic_counter_t refcount;
} link_message_t;
Generic part of a link message. This structure should always be the first member of the link message data structure, so that the link library can access the generic part with a simple cast.
The datagram_link_impl example defines its link message in the following way:
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;
link_type_t — functions to be defined by a specific link implementation
#include <simics/devs/liblink.h> // in C
import "simics/devs/liblink.dml"; // in DML
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;
Functions to be defined by the specific link implementation.
These functions can be classified in four groups:
The first five functions are related to the link-specific messages.
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_impl 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_impl 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 = nullptr;
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);
}
Link endpoints are created as needed by the link component. Depending on how they are created, they may not know yet which device they are connected to, so it might not be possible yet, for example, to cache the device’s communication interface in the endpoint’s finalize() function. Additionally, there are cases where the device the endpoint talks to may be changed, such as when inserting a probe object to listen to the traffic.
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-impl 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);
}
}
Messages are delivered to the link by calling the deliver() function. The arguments of deliver() are the endpoint ep that received the message and the message msg itself. The implementation of deliver() is expected to call the correct device’s function to deliver the message.
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_impl 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");
}
The last two functions of 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-impl 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, nullptr, 16);
SIM_LOG_INFO(4, &slink->obj, 0,
"Add endpoint: 0x%llx", ep_id);
ht_update_int(&slink->endpoints, ep_id, nullptr);
} else if (strcmp(key, "buffer_size") == 0) {
slink->buffer_size = frags_extract_be32(msg, 0);
} else {
ASSERT(false);
}
slink->connection_warning = 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, nullptr, 16);
SIM_LOG_INFO(4, &slink->obj, 0,
"Remove endpoint: 0x%llx", ep_id);
ht_remove_int(&slink->endpoints, ep_id);
} else {
ASSERT(false);
}
}