The link library lets users write Simics link models. A link is used to connect devices in different cells, even in different processes, so that they can exchange non-instantaneous messages in a deterministic way. Typical links include Ethernet and other network connections, but also simpler signal links and more abstract communication channels.
Much of the functionality of links are the same for different kinds of links; that commonality is encapsulated in the link library. This makes it simpler to create new link types, since a minimum of new code is required for each new link type.
Before going further, it is important to realize when not to use the link library:
datagram_link interface.The link library, however, is available for more complex usage: multiple message types across the link (such as link speed, link status, etc.), special addressing (unicast or multicast groups, switching, etc.), special types of devices (such as sender and receiver for the signal-link). All of these requirements will be more easily expressed, and provide better simulation performance, by creating a special-purpose link based on the link library.
The rest of this document describes how to create a new link type, using the provided datagram_link as an example. The reader is expected to know how to develop Simics modules in C. Further information can be found in the Model Builder User's Guide.
A link consists of two Simics configuration classes: link and endpoint. In a running simulation, there will exist one link object per link, and one endpoint object per device connected to the link.
The link and endpoint classes use the link library to handle many of the details of link operation. The link library keeps its state in opaque data structures attached to the link and endpoint objects. Some of this information can be queried via the link library API.
When devices connect to the link, they really contact a freshly created endpoint object, usually provided by the link component. Any interaction between the device and the link goes through that endpoint object.
When running in a distributed simulation, a link is represented by an identically named instance in each process of the simulation.
Link objects may have configuration parameters, which are represented as Simics attributes in the link class. These parameters should only be changed when the simulation is not running, to avoid indeterminism. In a distributed setup, configuration parameters should also be shared with the other instances of the same link: the link library provides the SIMLINK_config_update_value() and SIMLINK_config_remove_value() functions for that purpose. For example, the goal_latency of the link is implemented as a global configuration parameter. More information is available in chapter 8.
Endpoint objects may contain configuration parameters, as well as simulation state that can be changed at runtime, since they are only involved with one device on the link. For example, the ethernet_switch implementation keeps a translation table from MAC addresses to endpoint IDs in each endpoint on the link, allowing them to be updated as endpoints learn about the connected devices.
Linking the link library to your module is simply a matter of adding the
following line to the module's Makefile:
MODULE_LDFLAGS = -llink
The init_local() function in the link module needs to call the SIMLINK_init_library() function:
void
init_local(void)
{
/* The link library must always be initialised first. */
SIMLINK_init_library();
Each class defines a C struct type to store an instance of the
class. The struct must start with a conf_object_t,
and like any Simics configuration object it may also contain any additional
configuration parameters required by the specific link. As usual, if this
information needs to be saved in checkpoints, you as the class author are
responsible for registering attributes that save and restore it.
typedef struct {
conf_object_t obj;
/* Any link-specific parameters would go here. */
} datagram_link_t;
typedef struct {
conf_object_t obj;
/* Any endpoint-specific state would go here. */
} datagram_link_endpoint_t;
Each link type needs to declare a variable of type link_type_t and
fill it with information used by the link library to access functionality that
is specific to the link type. Each of the link_type_t member
functions is described in the chapter 8.
The link class's init() method needs to call SIMLINK_init() to attach all link library's internal information to the link object. The finalize() method needs to call SIMLINK_finalize().
static conf_object_t *
datagram_link_alloc_object(void *data)
{
datagram_link_t *dl = MM_ZALLOC(1, datagram_link_t);
return &dl->obj;
}
static void *
datagram_link_init_object(conf_object_t *obj, void *data)
{
datagram_link_t *dl = (datagram_link_t *)obj;
static const link_type_t link_methods = {
.msg_to_attr = msg_to_attr,
.msg_from_attr = msg_from_attr,
.free_msg = free_msg,
.marshal = marshal,
.unmarshal = unmarshal,
.deliver = deliver,
.update_config_value = link_config_value_updated,
.remove_config_value = link_config_value_removed,
};
SIMLINK_init(&dl->obj, &link_methods);
return &dl->obj;
}
static void
datagram_link_finalize_instance(conf_object_t *obj)
{
SIMLINK_finalize(obj);
}
Similarly, the endpoint class's init() and finalize() methods need to call SIMLINK_endpoint_init() and SIMLINK_endpoint_finalize(), respectively:
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;
}
static void
datagram_link_endpoint_finalize_instance(conf_object_t *ep)
{
SIMLINK_endpoint_finalize(ep);
}
link_type_t
documentation for more information and an example.After declaring the link class with SIM_create_class(), SIMLINK_register_class() must also be called, with the class as argument.
Similarly, for the endpoint class, SIMLINK_register_endpoint_class() must be called. It takes two parameters: the endpoint class and the attribute type of a checkpointed in-flight link message.
void
init_local(void)
{
/* The link library must always be initialised first. */
SIMLINK_init_library();
const class_data_t cl_methods = {
.alloc_object = datagram_link_alloc_object,
.init_object = datagram_link_init_object,
.finalize_instance = datagram_link_finalize_instance,
.pre_delete_instance = datagram_link_pre_delete_instance,
.delete_instance = datagram_link_delete_instance,
.class_desc = "link that broadcasts byte strings",
.description = "A link that broadcasts byte strings."
};
conf_class_t *cl = SIM_register_class("datagram_link_impl",
&cl_methods);
/* Tell the link library what class represents the link */
SIMLINK_register_class(cl);
const class_data_t epcl_methods = {
.alloc_object = datagram_link_endpoint_alloc_object,
.init_object = datagram_link_endpoint_init_object,
.finalize_instance = datagram_link_endpoint_finalize_instance,
.pre_delete_instance = SIMLINK_endpoint_disconnect,
.delete_instance = datagram_link_endpoint_delete_instance,
.class_desc = "endpoint for datagram links",
.description = "Endpoint for datagram link objects."
};
conf_class_t *epcl = SIM_register_class("datagram_link_endpoint",
&epcl_methods);
static const datagram_link_interface_t dgram_link_if = {
.receive = receive
};
SIM_register_interface(epcl, "datagram_link", &dgram_link_if);
/* Tell the link library what class we use for endpoints */
SIMLINK_register_endpoint_class(epcl, "d");
}
Endpoints always need to implement interfaces specific to the link type, so
they can accept messages from the connected
devices. Here, datagram_link registers
a datagram_link interface for its endpoint class.
void
init_local(void)
{
/* The link library must always be initialised first. */
SIMLINK_init_library();
const class_data_t cl_methods = {
.alloc_object = datagram_link_alloc_object,
.init_object = datagram_link_init_object,
.finalize_instance = datagram_link_finalize_instance,
.pre_delete_instance = datagram_link_pre_delete_instance,
.delete_instance = datagram_link_delete_instance,
.class_desc = "link that broadcasts byte strings",
.description = "A link that broadcasts byte strings."
};
conf_class_t *cl = SIM_register_class("datagram_link_impl",
&cl_methods);
/* Tell the link library what class represents the link */
SIMLINK_register_class(cl);
const class_data_t epcl_methods = {
.alloc_object = datagram_link_endpoint_alloc_object,
.init_object = datagram_link_endpoint_init_object,
.finalize_instance = datagram_link_endpoint_finalize_instance,
.pre_delete_instance = SIMLINK_endpoint_disconnect,
.delete_instance = datagram_link_endpoint_delete_instance,
.class_desc = "endpoint for datagram links",
.description = "Endpoint for datagram link objects."
};
conf_class_t *epcl = SIM_register_class("datagram_link_endpoint",
&epcl_methods);
static const datagram_link_interface_t dgram_link_if = {
.receive = receive
};
SIM_register_interface(epcl, "datagram_link", &dgram_link_if);
/* Tell the link library what class we use for endpoints */
SIMLINK_register_endpoint_class(epcl, "d");
}
The link model needs to define a structure that represents all kinds of
messages that can be sent over the link. This structure should "inherit"
from link_message_t, by placing it first in
the struct (so that a pointer to
a datagram_link_message_t can be cast to a
link_message_t pointer).
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;
New messages are allocated with MM_MALLOC(), and SIMLINK_init_message() is used to fill in the "common" part:
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;
}
Finally, the message is dispatched using the SIMLINK_send_message() or SIMLINK_send_message_multi() functions.
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));
}
Messages are delivered to the endpoints' deliver() callback registered by the link. The callback is responsible for translating the message into something the receiving device will understand.
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");
}
To be part of a standard, component-based configuration, new links have to provide a component that will wrap the link implementation and handle connections. An important role of the link component is to create and destroy endpoint objects as the connections are being made.
Links tend to share a lot of common functionality, and link components are no exceptions. The link library provides two interfaces to easily create link components:
The simplest way to create a link component is to use the link_components.create_simple() function. Here is the component definition for datagram_link:
from link_components import create_simple
class datagram_link(
create_simple(link_class = 'datagram_link_impl',
endpoint_class = 'datagram_link_endpoint',
connector_type = 'datagram-link',
class_desc = "datagram link",
basename = 'datagram_link')):
"""The datagram link component creates a datagram-link, which is a simple
broadcast bus forwarding messages (as sequences of bytes) from a sender
device to all other devices present of the link. The datagram-link is both
an example of how to build a link with the Simics Link Library, and a
simple broadcast link that can be reused when multi-cell communication
between devices is necessary. Refer to the <cite>Link Library Programming
Guide</cite> for more information."""
Using this simplified method assumes that the link fulfills a number of requirements:
Further information is available in the link_components.create_simple() API description, in chapter 8.
If the new link type does not comply with the limitations above, a slightly more complex interface is available.
When more flexibility is required, it is possible to create a new component class inheriting from link_components.link_component. Two functions should be overridden to control how the component handles connections:
class ethernet_switch(link_components.link_component):
"""Ethernet switch: this component represents a switched Ethernet network,
allowing any number of devices to connect and optimizing the packet routing
according to what is learned about the MAC addresses talking on the link."""
_class_desc = 'an Ethernet switch component'
_help_categories = ['Ethernet']
class basename(link_components.link_component.basename):
val = 'ethernet_switch'
def create_unconnected_endpoint(self, cnt):
return create_vlan_switch_endpoint(self.get_slot('link'), None,
None, True)
def register_connector_templates(self):
self.eth_tmpl = self.add_link_connector_template(
name = 'ethernet-link-connector',
type = 'ethernet-link',
growing = True,
create_unconnected_endpoint = self.create_unconnected_endpoint)
def add_objects(self):
self.add_pre_obj_with_name('link', 'eth-switch-link',
self.get_link_object_name(),
goal_latency = self.goal_latency.val,
global_id = self.global_id.val)
self.add_link_connector('device', self.eth_tmpl)
register_connector_templates() is responsible for registering the description—called template here—of the connectors that will be allowed in the link. Each connector template can be associated with a number of functions that will be called when a connection is attempted, is successful, or is destroyed. Each connector template can also be defined as growing, meaning that new connectors will be added dynamically to make sure there is always one connector free.
add_objects() is responsible for creating the necessary link object and the initial connectors.
A more complete explanation of these functions is available in the component API in chapter 8.
This chapter covers all the types and functions provided by the link library.
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);
}
}
create_simple(link_class, endpoint_class, connector_type,
class_desc, basename = None, help_categories = [])
from link_components import create_simple
class datagram_link(
create_simple(link_class = 'datagram_link_impl',
endpoint_class = 'datagram_link_endpoint',
connector_type = 'datagram-link',
class_desc = "datagram link",
basename = 'datagram_link')):
"""The datagram link component creates a datagram-link, which is a simple
broadcast bus forwarding messages (as sequences of bytes) from a sender
device to all other devices present of the link. The datagram-link is both
an example of how to build a link with the Simics Link Library, and a
simple broadcast link that can be reused when multi-cell communication
between devices is necessary. Refer to the <cite>Link Library Programming
Guide</cite> for more information."""
class ethernet_switch(link_components.link_component):
"""Ethernet switch: this component represents a switched Ethernet network,
allowing any number of devices to connect and optimizing the packet routing
according to what is learned about the MAC addresses talking on the link."""
_class_desc = 'an Ethernet switch component'
_help_categories = ['Ethernet']
class basename(link_components.link_component.basename):
val = 'ethernet_switch'
def create_unconnected_endpoint(self, cnt):
return create_vlan_switch_endpoint(self.get_slot('link'), None,
None, True)
def register_connector_templates(self):
self.eth_tmpl = self.add_link_connector_template(
name = 'ethernet-link-connector',
type = 'ethernet-link',
growing = True,
create_unconnected_endpoint = self.create_unconnected_endpoint)
def add_objects(self):
self.add_pre_obj_with_name('link', 'eth-switch-link',
self.get_link_object_name(),
goal_latency = self.goal_latency.val,
global_id = self.global_id.val)
self.add_link_connector('device', self.eth_tmpl)
add_link_connector(self, slot_template, cnt_tmpl)
class ethernet_switch(link_components.link_component):
"""Ethernet switch: this component represents a switched Ethernet network,
allowing any number of devices to connect and optimizing the packet routing
according to what is learned about the MAC addresses talking on the link."""
_class_desc = 'an Ethernet switch component'
_help_categories = ['Ethernet']
class basename(link_components.link_component.basename):
val = 'ethernet_switch'
def create_unconnected_endpoint(self, cnt):
return create_vlan_switch_endpoint(self.get_slot('link'), None,
None, True)
def register_connector_templates(self):
self.eth_tmpl = self.add_link_connector_template(
name = 'ethernet-link-connector',
type = 'ethernet-link',
growing = True,
create_unconnected_endpoint = self.create_unconnected_endpoint)
def add_objects(self):
self.add_pre_obj_with_name('link', 'eth-switch-link',
self.get_link_object_name(),
goal_latency = self.goal_latency.val,
global_id = self.global_id.val)
self.add_link_connector('device', self.eth_tmpl)
add_link_connector_template(self, name, type, growing,
create_unconnected_endpoint,
get_check_data = None,
get_connect_data = None,
check = None,
connect = None,
disconnect = None,
allow_new_cnt = lambda: True,
allow_destroy_cnt = lambda: True)
True
(connection accepted) or False (connection refused).
The standard implementation returns always True.True (new connector allowed) or False
(no new connector). The default function always returns
True (unlimited number of connectors allowed, with
always one free).True (destroy the connector) or False
(let the connector). The endpoint object associated will be
automatically destroyed with the connector, or replaced if the
connector is left. The default function returns always
True (unlimited number of connectors allowed, with
always one free).class ethernet_cable(link_components.link_component):
"""Ethernet cable: this component represents a two-points Ethernet cable,
allowing two devices to connect to each other."""
_class_desc = 'an Ethernet cable component'
_help_categories = ['Ethernet']
class basename(link_components.link_component.basename):
val = 'ethernet_cable'
class connector_count(SimpleAttribute(0, 'i')):
"""Total number of occupied connectors"""
def allow_new_connector(self):
if self.connector_count.val == 2:
# all connectors are occupied
return False
elif self.connector_count.val == 1:
# there is already one free connector
self.connector_count.val += 1
return False
else:
self.connector_count.val += 1
return True
def allow_destroy_connector(self):
if self.connector_count.val == 2:
# two connectors occupied, so let one become free
self.connector_count.val -= 1
return False
else:
# one connector was occupied, one free, so destroy one
self.connector_count.val -= 1
return True
def create_unconnected_endpoint(self, cnt):
return create_cable_endpoint(self.get_slot('link'), None)
def register_connector_templates(self):
self.eth_tmpl = self.add_link_connector_template(
name = 'single-ethernet-link-connector',
type = 'ethernet-link',
growing = True,
create_unconnected_endpoint = self.create_unconnected_endpoint,
allow_new_cnt = self.allow_new_connector,
allow_destroy_cnt = self.allow_destroy_connector)
def add_objects(self):
self.add_pre_obj_with_name('link', 'eth-cable-link',
self.get_link_object_name(),
goal_latency = self.goal_latency.val,
global_id = self.global_id.val)
self.add_link_connector('device', self.eth_tmpl)
add_objects(self)
class ethernet_switch(link_components.link_component):
"""Ethernet switch: this component represents a switched Ethernet network,
allowing any number of devices to connect and optimizing the packet routing
according to what is learned about the MAC addresses talking on the link."""
_class_desc = 'an Ethernet switch component'
_help_categories = ['Ethernet']
class basename(link_components.link_component.basename):
val = 'ethernet_switch'
def create_unconnected_endpoint(self, cnt):
return create_vlan_switch_endpoint(self.get_slot('link'), None,
None, True)
def register_connector_templates(self):
self.eth_tmpl = self.add_link_connector_template(
name = 'ethernet-link-connector',
type = 'ethernet-link',
growing = True,
create_unconnected_endpoint = self.create_unconnected_endpoint)
def add_objects(self):
self.add_pre_obj_with_name('link', 'eth-switch-link',
self.get_link_object_name(),
goal_latency = self.goal_latency.val,
global_id = self.global_id.val)
self.add_link_connector('device', self.eth_tmpl)
get_link_object_name(self)
class ethernet_switch(link_components.link_component):
"""Ethernet switch: this component represents a switched Ethernet network,
allowing any number of devices to connect and optimizing the packet routing
according to what is learned about the MAC addresses talking on the link."""
_class_desc = 'an Ethernet switch component'
_help_categories = ['Ethernet']
class basename(link_components.link_component.basename):
val = 'ethernet_switch'
def create_unconnected_endpoint(self, cnt):
return create_vlan_switch_endpoint(self.get_slot('link'), None,
None, True)
def register_connector_templates(self):
self.eth_tmpl = self.add_link_connector_template(
name = 'ethernet-link-connector',
type = 'ethernet-link',
growing = True,
create_unconnected_endpoint = self.create_unconnected_endpoint)
def add_objects(self):
self.add_pre_obj_with_name('link', 'eth-switch-link',
self.get_link_object_name(),
goal_latency = self.goal_latency.val,
global_id = self.global_id.val)
self.add_link_connector('device', self.eth_tmpl)
register_connector_templates(self)
class ethernet_switch(link_components.link_component):
"""Ethernet switch: this component represents a switched Ethernet network,
allowing any number of devices to connect and optimizing the packet routing
according to what is learned about the MAC addresses talking on the link."""
_class_desc = 'an Ethernet switch component'
_help_categories = ['Ethernet']
class basename(link_components.link_component.basename):
val = 'ethernet_switch'
def create_unconnected_endpoint(self, cnt):
return create_vlan_switch_endpoint(self.get_slot('link'), None,
None, True)
def register_connector_templates(self):
self.eth_tmpl = self.add_link_connector_template(
name = 'ethernet-link-connector',
type = 'ethernet-link',
growing = True,
create_unconnected_endpoint = self.create_unconnected_endpoint)
def add_objects(self):
self.add_pre_obj_with_name('link', 'eth-switch-link',
self.get_link_object_name(),
goal_latency = self.goal_latency.val,
global_id = self.global_id.val)
self.add_link_connector('device', self.eth_tmpl)