Link Library Programming Guide

1 Introduction

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:

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.

2 Link Concepts

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.

3 Linking and Initialization

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();

4 Class Definitions

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);
}

Note: Depending on the current configuration, endpoint objects might be created while no device is yet connected. For that reason, device related operations, such as caching an interface, are expected to be done in the device_changed() callback rather than in the finalize() function. See also link_type_t documentation for more information and an example.
The endpoint class also has to have a deinit() function, which has to be, or at least call, SIMLINK_endpoint_disconnect().

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");
}
                                                                 

5 Link-Specific Interfaces

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");
}
                                                                 

6 Communication Interfaces

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");
}

7 Link Components

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:

Simple Component

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.

Custom Component

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.

8 Link Library API

This chapter covers all the types and functions provided by the link library.

Link Library API

SIMLINK_config_remove_value()

NAME
SIMLINK_config_remove_value — remove a link configuration parameter
SYNOPSIS
void
SIMLINK_config_remove_value(conf_object_t *link, const char *key);

DESCRIPTION
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.

RETURN VALUE
None
EXECUTION CONTEXT
Global Context
EXAMPLE
Example from ser_link, where the ID of the endpoints present on the link is kept as a configuration parameter:
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);
}

SEE ALSO
SIMLINK_config_update_value, link_type_t

SIMLINK_config_update_value()

NAME
SIMLINK_config_update_value — update a link configuration parameter
SYNOPSIS
void
SIMLINK_config_update_value(conf_object_t *link, const char *key,
                            const frags_t *value);

DESCRIPTION
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.

RETURN VALUE
None
EXECUTION CONTEXT
Global Context
EXAMPLE
Example from ethernet_switch, 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_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);
}

SEE ALSO
SIMLINK_config_remove_value, link_type_t

SIMLINK_endpoint_clock()

NAME
SIMLINK_endpoint_clock — return endpoint's clock
SYNOPSIS
conf_object_t *
SIMLINK_endpoint_clock(const conf_object_t *ep_obj);

DESCRIPTION
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.
RETURN VALUE
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.
EXECUTION CONTEXT
Cell Context
SEE ALSO
SIMLINK_endpoint_device

SIMLINK_endpoint_dev_name()

NAME
SIMLINK_endpoint_dev_name — return the name of the device or snooper to which an endpoint is connected
SYNOPSIS
const char *
SIMLINK_endpoint_dev_name(const conf_object_t *ep_obj, buffer_t scratch);

DESCRIPTION
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.

RETURN VALUE
The name of the device or snooper the endpoint is connected to
EXECUTION CONTEXT
Cell Context
EXAMPLE
Example from ethernet_hub, 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)));

SEE ALSO
SIMLINK_endpoint_is_device, SIMLINK_endpoint_device

SIMLINK_endpoint_device()

NAME
SIMLINK_endpoint_device — return the device to which an endpoint is connected
SYNOPSIS
conf_object_t *
SIMLINK_endpoint_device(const conf_object_t *ep_obj);

DESCRIPTION
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().
RETURN VALUE
The device to which the endpoint is connected
EXECUTION CONTEXT
Cell Context
EXAMPLE
Example from datagram_link:
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");
}

SEE ALSO
SIMLINK_endpoint_is_device, SIMLINK_endpoint_dev_name

SIMLINK_endpoint_disconnect()

NAME
SIMLINK_endpoint_disconnect — disconnect an endpoint object
SYNOPSIS
void
SIMLINK_endpoint_disconnect(conf_object_t *ep_obj);

DESCRIPTION
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.
RETURN VALUE
None
EXECUTION CONTEXT
Global Context
EXAMPLE
Example from ser_link:
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);
}

SIMLINK_endpoint_finalize()

NAME
SIMLINK_endpoint_finalize — finalize an endpoint object
SYNOPSIS
void
SIMLINK_endpoint_finalize(conf_object_t *ep_obj);

DESCRIPTION
Finalize the endpoint object ep_obj. This function is intended to be called in the finalize_instance() method of an endpoint class.
RETURN VALUE
None
EXECUTION CONTEXT
Global Context
EXAMPLE
Example from ser_link:
static void
ser_link_ep_finalize_instance(conf_object_t *ep)
{
        SIMLINK_endpoint_finalize(ep);
}

SEE ALSO
SIMLINK_endpoint_init

SIMLINK_endpoint_id()

NAME
SIMLINK_endpoint_id — return endpoint's ID
SYNOPSIS
uint64
SIMLINK_endpoint_id(const conf_object_t *ep);

DESCRIPTION
Return the endpoint ep's ID.
RETURN VALUE
The endpoint ID
EXECUTION CONTEXT
Cell Context
EXAMPLE
In ser_link, the endpoints IDs are kept as configuration values so they are known in all link objects:
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);
}

SEE ALSO
SIMLINK_find_endpoint_by_id

SIMLINK_endpoint_init()

NAME
SIMLINK_endpoint_init — initialize an endpoint object
SYNOPSIS
void
SIMLINK_endpoint_init(conf_object_t *obj, bool snoop);

DESCRIPTION
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.
RETURN VALUE
None
EXECUTION CONTEXT
Global Context
EXAMPLE
Example from datagram_link:
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;
}

SEE ALSO
SIMLINK_register_endpoint_class, SIMLINK_register_snoop_endpoint_class, SIMLINK_endpoint_finalize

SIMLINK_endpoint_is_device()

NAME
SIMLINK_endpoint_is_device — return whether an endpoint is connected to a device
SYNOPSIS
bool
SIMLINK_endpoint_is_device(const conf_object_t *ep);

DESCRIPTION
Return whether the endpoint ep is connected to a device (as opposed to a link snooper).
RETURN VALUE
true if the endpoint is connected to a device, false otherwise
EXECUTION CONTEXT
Cell Context
EXAMPLE
Example from ethernet_switch, 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);
        }
}

SEE ALSO
SIMLINK_endpoint_device, SIMLINK_endpoint_port

SIMLINK_endpoint_link()

NAME
SIMLINK_endpoint_link — return endpoint's link
SYNOPSIS
conf_object_t *
SIMLINK_endpoint_link(const conf_object_t *ep);

DESCRIPTION
Return the link object to which the endpoint ep is connected.
RETURN VALUE
The link object
EXECUTION CONTEXT
Cell Context
EXAMPLE
In ser_link, 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) {
        case MSG_Char:

SIMLINK_endpoint_port()

NAME
SIMLINK_endpoint_port — return the device's port to which an endpoint is connected
SYNOPSIS
const char *
SIMLINK_endpoint_port(const conf_object_t *ep_obj);

DESCRIPTION
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.

RETURN VALUE
The device's port to which the endpoint is connected, or NULL if no port is used
EXECUTION CONTEXT
Cell Context
EXAMPLE
Example from ser_link, where a pointer to the interface to call for delivery is kept in the endpoint structure:
static void
ser_link_ep_finalize_instance(conf_object_t *ep)
{
        SIMLINK_endpoint_finalize(ep);
}

SEE ALSO
SIMLINK_endpoint_is_device, SIMLINK_endpoint_device

SIMLINK_finalize()

NAME
SIMLINK_finalize — finalize a link object
SYNOPSIS
void
SIMLINK_finalize(conf_object_t *obj);

DESCRIPTION
Finalize the link object obj. This function is intended to be called in the finalize_instance() method of a link class.
RETURN VALUE
None
EXECUTION CONTEXT
Global Context
EXAMPLE
Example from ser_link:
static void
datagram_link_finalize_instance(conf_object_t *obj)
{
        SIMLINK_finalize(obj);
}

SEE ALSO
SIMLINK_init

SIMLINK_find_endpoint_by_id()

NAME
SIMLINK_find_endpoint_by_id — return an endpoint object given its ID
SYNOPSIS
conf_object_t *
SIMLINK_find_endpoint_by_id(conf_object_t *link, uint64 id);

DESCRIPTION
Return the endpoint object with the ID id if the endpoint is connected to the link link, or NULL otherwise.
RETURN VALUE
Endpoint object, or NULL if not found
EXECUTION CONTEXT
Cell Context
SEE ALSO
SIMLINK_endpoint_id

SIMLINK_init()

NAME
SIMLINK_init — initialize a link object
SYNOPSIS
void
SIMLINK_init(conf_object_t *obj, const link_type_t *type);

DESCRIPTION
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.
RETURN VALUE
None
EXECUTION CONTEXT
Global Context
EXAMPLE
Example from ser_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;
}

SEE ALSO
SIMLINK_register_class, SIMLINK_finalize, link_type_t

SIMLINK_init_library()

NAME
SIMLINK_init_library — initialize the link library
SYNOPSIS
void
SIMLINK_init_library(void);

DESCRIPTION
Initialize the link library. This function is meant to be called in the init_local() function of a module linked to the library.
RETURN VALUE
None
EXECUTION CONTEXT
Global Context
EXAMPLE
Example from datagram_link:
void
init_local(void)
{
        /* The link library must always be initialised first. */
        SIMLINK_init_library();

SIMLINK_init_message()

NAME
SIMLINK_init_message — initialize a link message
SYNOPSIS
void
SIMLINK_init_message(link_message_t *msg);

DESCRIPTION
Initialize the generic part of a link message.
RETURN VALUE
None
EXECUTION CONTEXT
Cell Context
EXAMPLE
Example from datagram_link:
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;
}
                                                      

SEE ALSO
link_type_t

SIMLINK_pre_delete()

NAME
SIMLINK_pre_delete — clean-up before link deletion
SYNOPSIS
void
SIMLINK_pre_delete(conf_object_t *obj);

DESCRIPTION
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.
RETURN VALUE
None
EXECUTION CONTEXT
Global Context
EXAMPLE
Example from ser_link:
static void
ser_link_pre_delete_instance(conf_object_t *obj)
{
        SIMLINK_pre_delete(obj);
}

SIMLINK_register_class()

NAME
SIMLINK_register_class — register a link class
SYNOPSIS
void
SIMLINK_register_class(conf_class_t *cls);

DESCRIPTION
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.
RETURN VALUE
None
EXECUTION CONTEXT
Global Context
EXAMPLE
Example from ser_link:
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]");
        

SEE ALSO
SIMLINK_init, SIMLINK_finalize

SIMLINK_register_endpoint_class()

NAME
SIMLINK_register_endpoint_class — register a link endpoint class
SYNOPSIS
void
SIMLINK_register_endpoint_class(conf_class_t *cls, const char *msg_type);

DESCRIPTION
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.

RETURN VALUE
None
EXECUTION CONTEXT
Global Context
EXAMPLE
Example from ser_link:
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]");
        

SEE ALSO
SIMLINK_endpoint_finalize

SIMLINK_register_snoop_endpoint_class()

NAME
SIMLINK_register_snoop_endpoint_class — register a link snoop endpoint class
SYNOPSIS
void
SIMLINK_register_snoop_endpoint_class(conf_class_t *cls);

DESCRIPTION
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().
RETURN VALUE
None
EXECUTION CONTEXT
Global Context
EXAMPLE
Example from the new Ethernet links:
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);
}

SEE ALSO
SIMLINK_endpoint_finalize

SIMLINK_send_message()

NAME
SIMLINK_send_message — send a link message
SYNOPSIS
void
SIMLINK_send_message(conf_object_t *src_ep_obj,
                  uint64 dst_id, link_message_t *msg);

DESCRIPTION
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.

RETURN VALUE
None
EXECUTION CONTEXT
Cell Context
EXAMPLE
Example from datagram_link:
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));
}

SEE ALSO
SIMLINK_send_message_multi, link_message_t, link_type_t

SIMLINK_send_message_multi()

NAME
SIMLINK_send_message_multi — send a link message to multiple recipients
SYNOPSIS
void
SIMLINK_send_message_multi(conf_object_t *src_ep_obj, unsigned num_dsts,
                           const uint64 *dst_ids, link_message_t *msg);

DESCRIPTION
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.

RETURN VALUE
None
EXECUTION CONTEXT
Cell Context
EXAMPLE
Example from signal_link, 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);
}

SEE ALSO
SIMLINK_send_message, link_message_t, link_type_t

SIMLINK_snoop_endpoint_create()

NAME
SIMLINK_snoop_endpoint_create — create a snoop endpoint object
SYNOPSIS
conf_object_t *
SIMLINK_snoop_endpoint_create(conf_class_t *cls, conf_object_t *link,
                              conf_object_t *clock,
                              attr_value_t attrs);

DESCRIPTION
This method returns an already created snoop endpoint object. It is meant to be used when implementing a 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().
RETURN VALUE
A snoop endpoint object
EXECUTION CONTEXT
Global Context
EXAMPLE
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

NAME
link_message_t
SYNOPSIS
typedef struct link_message link_message_t;
DESCRIPTION
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.
EXAMPLE
The datagram_link 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;

SEE ALSO
link_type_t, SIMLINK_init_message

link_type_t

NAME
link_type_t
SYNOPSIS
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;

DESCRIPTION
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 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);
}

Endpoint Configuration
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 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);
        }
}

Message Delivery
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 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");
}

Configuration
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 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);
        }
}

SEE ALSO
link_message_t, SIMLINK_init

Link Component API

link_components.create_simple()

NAME
create_simple — create a simple link component class
SYNOPSIS
create_simple(link_class, endpoint_class, connector_type,
              class_desc, basename = None, help_categories = [])

DESCRIPTION
Create a simple link component class based on the following parameters:

Name of the link implementation class
endpoint_class
Name of the link endpoint class
connector_type
Name of the connector type for component connections
class_desc
Component description
basename
Prefix used to create new component names when none is provided

RETURN VALUE
A new component class from which to inherit
EXAMPLE
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."""

link_components.link_component

NAME
link_component — link components base class
DESCRIPTION
Class from which to inherit when creating a new custom link component.
EXAMPLE
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)

link_components.link_component.add_link_connector()

NAME
add_link_connector — add a new initial connector
SYNOPSIS
add_link_connector(self, slot_template, cnt_tmpl)

DESCRIPTION
Add a new initial connector. The slot_template argument is the name of the connector in the component. The cnt_tmpl argument is the template used for the connector, previously registered with add_connector_template().
RETURN VALUE
None
EXAMPLE
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)

link_components.link_component.add_link_connector_template()

NAME
add_link_connector_template — add a link connector template
SYNOPSIS
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)

DESCRIPTION
This function registers a new connector template for the component. From this template, connectors will be created either statically, via the add_objects() function, or dynamically if requested. Component templates can be customized through the parameters of add_link_connector_template():

name
is the name of the template, which will be saved in each connector, so that they can find out from which template they were created.
type
is the connector type.
growing
indicates whether the connector is static, or should grow dynamically as connections are made. Static connectors must be created in add_objects(), and will act as classic component connectors. A dynamic connector will make sure that there is always a free connector of that template available, by increasing or decreasing the number of connectors of this template in the link. Note that several templates can have the same connector type. Each template will make sure that its connectors grow or shrink separately.
create_unconnected_endpoint
is the function to call when a new endpoint pre-conf-object must be created. This endpoint is not yet connected to a device.
get_check_data
(optional) is called whenever the standard get_check_data() is called. It may return any additional data necessary for the check() call. The standard get_check_data() will already return the endpoint object.
get_connect_data
(optional) is similar to get_check_data, but for the connect() call.
check
(optional) is called whenever the standard check() is called. It may return True (connection accepted) or False (connection refused). The standard implementation returns always True.
connect
(optional) is called whenever the standard connect() is called. The standard connect() will set the device attribute in the endpoint. connect may take any additional action it deems necessary.
disconnect
(optional) is called whenever the standard disconnect() is called. The standard disconnect() does not do anything as the endpoint object will be destroyed soon after. disconnect() may take any additional action for the disconnection to succeed.
allow_new_nct
(optional) is used only for growing connectors. It is called every time a new connection is made to ask if creating a new empty connector is allowed. It may return True (new connector allowed) or False (no new connector). The default function always returns True (unlimited number of connectors allowed, with always one free).
allow_destroy_cnt
(optional) is used only for growing connectors. It is called every time a connection is severed to ask if the connector being disconnected should be destroyed. It may return 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).

RETURN VALUE
The registered connector template
EXAMPLE
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)

link_components.link_component.add_objects()

NAME
add_objects — add link object and initial connectors
SYNOPSIS
add_objects(self)

DESCRIPTION
This function should be overridden when inheriting from link_component. It is expected to create a pre-conf-object for the link and to add the initial connectors of the component using link_component.add_link_connector(). add_objects() is only called when creating a component from scratch; when restoring a checkpoint, objects are assumed to have already been created.
RETURN VALUE
None
EXAMPLE
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)

link_components.link_component.get_link_object_name()

NAME
get_link_object_name — return a unique link object name
SYNOPSIS
get_link_object_name(self)

DESCRIPTION
Return a unique link object name based on the link component name. This is useful for ensuring that all link components with the same name in a distributed simulation will indeed represent the same link.
RETURN VALUE
A unique link name
EXAMPLE
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)

link_components.link_component.register_connector_templates()

NAME
register_connector_templates — register connector templates
SYNOPSIS
register_connector_templates(self)

DESCRIPTION
This function should be overridden when inheriting from link_component. It is expected to register the connector templates that will be used in add_objects(). Unlike add_objects(), this function is always called when creating the component, either from scratch or when restoring a checkpoint.
RETURN VALUE
None
EXAMPLE
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)