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

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 (for GNU Make builds):

MODULE_LDFLAGS = -llink

For CMake-based builds, use target_link_libraries to link against the link library instead.

The init_local() function in the link module needs to call the SIMLINK_init_library() function:

void
init_local()
{
        /* The link library must always be initialised first. */
        SIMLINK_init_library();

        /* The rest of the module initialization */
}

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 Link Library API reference.

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(conf_class_t *cls)
{
        datagram_link_t *dl = MM_ZALLOC(1, datagram_link_t);
        return &dl->obj;
}

static void *
datagram_link_init(conf_object_t *obj)
{
        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(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(conf_object_t *obj)
{
        datagram_link_endpoint_t *dlep =
                (datagram_link_endpoint_t *)obj;
        SIMLINK_endpoint_init(&dlep->obj, false);
        return dlep;
}
static void
datagram_link_endpoint_finalize(conf_object_t *ep)
{
        SIMLINK_endpoint_finalize(ep);
}

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()
{
        /* The link library must always be initialised first. */
        SIMLINK_init_library();

        const class_info_t cl_methods = {
                .alloc = datagram_link_alloc,
                .init = datagram_link_init,
                .finalize = datagram_link_finalize,
                .deinit = datagram_link_deinit,
                .dealloc = datagram_link_dealloc,
                .short_desc = "link that broadcasts byte strings",
                .description = "A link that broadcasts byte strings."
        };
        conf_class_t *cl = SIM_create_class("datagram_link_impl",
                                              &cl_methods);

        /* Tell the link library what class represents the link */
        SIMLINK_register_class(cl);

        const class_info_t epcl_methods = {
                .alloc = datagram_link_endpoint_alloc,
                .init = datagram_link_endpoint_init,
                .finalize = datagram_link_endpoint_finalize,
                .deinit = SIMLINK_endpoint_disconnect,
                .dealloc = datagram_link_endpoint_dealloc,
                .short_desc = "endpoint for datagram links",
                .description = "Endpoint for datagram link objects."
        };
        conf_class_t *epcl = SIM_create_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");
}