|
Packit |
534379 |
# Plugin Developer's Guide #
|
|
Packit |
534379 |
|
|
Packit |
534379 |
```eval_rst
|
|
Packit |
534379 |
.. toctree::
|
|
Packit |
534379 |
```
|
|
Packit |
534379 |
|
|
Packit |
534379 |
## Overview ##
|
|
Packit |
534379 |
|
|
Packit |
534379 |
Beginning with OPAE C library version 1.2.0, OPAE implements a plugin-centric
|
|
Packit |
534379 |
model. This guide serves as a reference to define the makeup of an OPAE C API
|
|
Packit |
534379 |
plugin and to describe a sequence of steps that one may follow when constructing
|
|
Packit |
534379 |
an OPAE C API plugin.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
## Plugin Required Functions ##
|
|
Packit |
534379 |
|
|
Packit |
534379 |
An OPAE C API plugin is a runtime-loadable shared object library, also known as
|
|
Packit |
534379 |
a module. On Linux systems, the *dl* family of APIs from libdl are used to
|
|
Packit |
534379 |
interact with shared objects. Refer to "man dlopen" and "man dlsym" for examples
|
|
Packit |
534379 |
of using the libdl API.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
An OPAE C API plugin implements one required function. This function is required
|
|
Packit |
534379 |
to have C linkage, so that its name is not mangled.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
```c
|
|
Packit |
534379 |
int opae_plugin_configure(opae_api_adapter_table *table, const char *config);
|
|
Packit |
534379 |
```
|
|
Packit |
534379 |
|
|
Packit |
534379 |
During initialization, the OPAE plugin manager component loads each plugin,
|
|
Packit |
534379 |
searching for its `opae_plugin_configure` function. If none is found, then
|
|
Packit |
534379 |
the plugin manager rejects that plugin. When it is found, `opae_plugin_configure`
|
|
Packit |
534379 |
is called passing a pointer to a freshly-created `opae_api_adapter_table` and
|
|
Packit |
534379 |
a buffer consisting of configuration data for the plugin.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
The job of the `opae_plugin_configure` function is to populate the given adapter
|
|
Packit |
534379 |
table with each of the plugin's API entry points and to consume and comprehend
|
|
Packit |
534379 |
the given configuration data in preparation for initialization.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
## OPAE API Adapter Table ##
|
|
Packit |
534379 |
|
|
Packit |
534379 |
The adapter table is a data structure that contains function pointer entry points
|
|
Packit |
534379 |
for each of the OPAE APIs implemented by a plugin. In this way, it adapts the
|
|
Packit |
534379 |
plugin-specific behavior to the more general case of a flat C API. Note that
|
|
Packit |
534379 |
OPAE applications are only required to link with opae-c. In other words, the
|
|
Packit |
534379 |
name of the plugin library should not appear on the linker command line. In this
|
|
Packit |
534379 |
way, plugins are truly decoupled from the OPAE C API, and they are required to
|
|
Packit |
534379 |
adapt to the strict API specification by populating the adapter table only. No
|
|
Packit |
534379 |
other linkage is required nor recommended.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
`adapter.h` contains the definition of the `opae_api_adapter_table`. An abbreviated
|
|
Packit |
534379 |
version is depicted below, along with supporting type `opae_plugin`:
|
|
Packit |
534379 |
|
|
Packit |
534379 |
```c
|
|
Packit |
534379 |
typedef struct _opae_plugin {
|
|
Packit |
534379 |
char *path;
|
|
Packit |
534379 |
void *dl_handle;
|
|
Packit |
534379 |
} opae_plugin;
|
|
Packit |
534379 |
|
|
Packit |
534379 |
typedef struct _opae_api_adapter_table {
|
|
Packit |
534379 |
|
|
Packit |
534379 |
struct _opae_api_adapater_table *next;
|
|
Packit |
534379 |
opae_plugin plugin;
|
|
Packit |
534379 |
|
|
Packit |
534379 |
fpga_result (*fpgaOpen)(fpga_token token, fpga_handle *handle,
|
|
Packit |
534379 |
int flags);
|
|
Packit |
534379 |
|
|
Packit |
534379 |
fpga_result (*fpgaClose)(fpga_handle handle);
|
|
Packit |
534379 |
|
|
Packit |
534379 |
...
|
|
Packit |
534379 |
|
|
Packit |
534379 |
fpga_result (*fpgaEnumerate)(const fpga_properties *filters,
|
|
Packit |
534379 |
uint32_t num_filters, fpga_token *tokens,
|
|
Packit |
534379 |
uint32_t max_tokens,
|
|
Packit |
534379 |
uint32_t *num_matches);
|
|
Packit |
534379 |
|
|
Packit |
534379 |
...
|
|
Packit |
534379 |
|
|
Packit |
534379 |
// configuration functions
|
|
Packit |
534379 |
int (*initialize)(void);
|
|
Packit |
534379 |
int (*finalize)(void);
|
|
Packit |
534379 |
|
|
Packit |
534379 |
// first-level query
|
|
Packit |
534379 |
bool (*supports_device)(const char *device_type);
|
|
Packit |
534379 |
bool (*supports_host)(const char *hostname);
|
|
Packit |
534379 |
|
|
Packit |
534379 |
} opae_api_adapter_table;
|
|
Packit |
534379 |
```
|
|
Packit |
534379 |
|
|
Packit |
534379 |
Some points worth noting are that the adapter tables are organized in memory by
|
|
Packit |
534379 |
adding them to a linked list data structure. This is the use of the `next`
|
|
Packit |
534379 |
structure member. (The list management is handled by the plugin manager.)
|
|
Packit |
534379 |
The `plugin` structure member contains the handle to the shared object instance,
|
|
Packit |
534379 |
as created by `dlopen`. This handle is used in the plugin's `opae_plugin_configure`
|
|
Packit |
534379 |
to load plugin entry points. A plugin need only implement the portion of the
|
|
Packit |
534379 |
OPAE C API that a target application needs. Any API entry points that are not
|
|
Packit |
534379 |
supported should be left as NULL pointers (the default) in the adapter table.
|
|
Packit |
534379 |
When an OPAE API that has no associated entry point in the adapter table is
|
|
Packit |
534379 |
called, the result for objects associated with that plugin will be
|
|
Packit |
534379 |
`FPGA_NOT_SUPPORTED`.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
The following code illustrates a portion of the `opae_plugin_configure` for
|
|
Packit |
534379 |
a theoretical OPAE C API plugin libfoo.so:
|
|
Packit |
534379 |
|
|
Packit |
534379 |
```c
|
|
Packit |
534379 |
/* foo_plugin.c */
|
|
Packit |
534379 |
|
|
Packit |
534379 |
int opae_plugin_configure(opae_api_adapter_table *table, const char *config)
|
|
Packit |
534379 |
{
|
|
Packit |
534379 |
adapter->fpgaOpen = dlsym(adapter->plugin.dl_handle, "foo_fpgaOpen");
|
|
Packit |
534379 |
adapter->fpgaClose =
|
|
Packit |
534379 |
dlsym(adapter->plugin.dl_handle, "foo_fpgaClose");
|
|
Packit |
534379 |
|
|
Packit |
534379 |
...
|
|
Packit |
534379 |
|
|
Packit |
534379 |
adapter->fpgaEnumerate =
|
|
Packit |
534379 |
dlsym(adapter->plugin.dl_handle, "foo_fpgaEnumerate");
|
|
Packit |
534379 |
|
|
Packit |
534379 |
...
|
|
Packit |
534379 |
|
|
Packit |
534379 |
return 0;
|
|
Packit |
534379 |
}
|
|
Packit |
534379 |
```
|
|
Packit |
534379 |
|
|
Packit |
534379 |
Notice that the implementations of the API entry points for plugin libfoo.so
|
|
Packit |
534379 |
are prefixed with `foo_`. This is the recommended practice to avoid name
|
|
Packit |
534379 |
collisions and to enhance the debugability of the application. Upon successful
|
|
Packit |
534379 |
configuration, `opae_plugin_configure` returns 0 to indicate success. A
|
|
Packit |
534379 |
non-zero return value indicates failure and causes the plugin manager to
|
|
Packit |
534379 |
reject the plugin from futher consideration.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
## Plugin Optional Functions ##
|
|
Packit |
534379 |
|
|
Packit |
534379 |
Once the plugin manager loads and configures each plugin, it uses the adapter
|
|
Packit |
534379 |
table to call back into the plugin so that it can be made ready for runtime.
|
|
Packit |
534379 |
This is the job of the `opae_plugin_initialize` entry point, whose signature
|
|
Packit |
534379 |
is defined as:
|
|
Packit |
534379 |
|
|
Packit |
534379 |
```c
|
|
Packit |
534379 |
int opae_plugin_initialize(void);
|
|
Packit |
534379 |
```
|
|
Packit |
534379 |
|
|
Packit |
534379 |
The function takes no parameters, as the configuration data was already given
|
|
Packit |
534379 |
to the plugin by `opae_plugin_configure`. `opae_plugin_initialize` returns 0
|
|
Packit |
534379 |
if no errors were encountered during initialization. A non-zero return code
|
|
Packit |
534379 |
indicates that plugin initialization failed. A plugin makes its
|
|
Packit |
534379 |
`opae_plugin_initialize` available to the plugin manager by populating the
|
|
Packit |
534379 |
adapter table's `initialize` entry point as shown:
|
|
Packit |
534379 |
|
|
Packit |
534379 |
```c
|
|
Packit |
534379 |
/* foo_plugin.c */
|
|
Packit |
534379 |
|
|
Packit |
534379 |
int foo_plugin_initialize(void)
|
|
Packit |
534379 |
{
|
|
Packit |
534379 |
...
|
|
Packit |
534379 |
|
|
Packit |
534379 |
return 0; /* success */
|
|
Packit |
534379 |
}
|
|
Packit |
534379 |
|
|
Packit |
534379 |
int opae_plugin_configure(opae_api_adapter_table *table, const char *config)
|
|
Packit |
534379 |
{
|
|
Packit |
534379 |
...
|
|
Packit |
534379 |
|
|
Packit |
534379 |
adapter->initialize =
|
|
Packit |
534379 |
dlsym(adapter->plugin.dl_handle, "foo_plugin_initialize");
|
|
Packit |
534379 |
|
|
Packit |
534379 |
...
|
|
Packit |
534379 |
|
|
Packit |
534379 |
return 0;
|
|
Packit |
534379 |
}
|
|
Packit |
534379 |
```
|
|
Packit |
534379 |
|
|
Packit |
534379 |
If a plugin does not implement an `opae_plugin_initialize` entry point, then
|
|
Packit |
534379 |
the `initialize` member of the adapter table should be left uninitialized.
|
|
Packit |
534379 |
During plugin initialization, if a plugin has no `opae_plugin_initialize`
|
|
Packit |
534379 |
entry in its adapter table, the plugin initialization step will be skipped,
|
|
Packit |
534379 |
and the plugin will be considered to have initialized successfully.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
Once plugin initialization is complete for all loaded plugins, the system
|
|
Packit |
534379 |
is considered to be running and fully functional.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
During teardown, the plugin manager uses the adapter table to call into each
|
|
Packit |
534379 |
plugin's `opae_plugin_finalize` entry point, whose signature is defined as:
|
|
Packit |
534379 |
|
|
Packit |
534379 |
```c
|
|
Packit |
534379 |
int opae_plugin_finalize(void);
|
|
Packit |
534379 |
```
|
|
Packit |
534379 |
|
|
Packit |
534379 |
`opae_plugin_finalize` returns 0 if no errors were encountered during teardown.
|
|
Packit |
534379 |
A non-zero return code indicates that plugin teardown failed. A plugin makes
|
|
Packit |
534379 |
its `opae_plugin_finalize` available to the plugin manager by populating the
|
|
Packit |
534379 |
adapter table's `finalize` entry point as shown:
|
|
Packit |
534379 |
|
|
Packit |
534379 |
```c
|
|
Packit |
534379 |
/* foo_plugin.c */
|
|
Packit |
534379 |
|
|
Packit |
534379 |
int foo_plugin_finalize(void)
|
|
Packit |
534379 |
{
|
|
Packit |
534379 |
...
|
|
Packit |
534379 |
|
|
Packit |
534379 |
return 0; /* success */
|
|
Packit |
534379 |
}
|
|
Packit |
534379 |
|
|
Packit |
534379 |
int opae_plugin_configure(opae_api_adapter_table *table, const char *config)
|
|
Packit |
534379 |
{
|
|
Packit |
534379 |
...
|
|
Packit |
534379 |
|
|
Packit |
534379 |
adapter->finalize =
|
|
Packit |
534379 |
dlsym(adapter->plugin.dl_handle, "foo_plugin_finalize");
|
|
Packit |
534379 |
|
|
Packit |
534379 |
...
|
|
Packit |
534379 |
|
|
Packit |
534379 |
return 0;
|
|
Packit |
534379 |
}
|
|
Packit |
534379 |
```
|
|
Packit |
534379 |
|
|
Packit |
534379 |
If a plugin does not implement an `opae_plugin_finalize` entry point, then
|
|
Packit |
534379 |
the `finalize` member of the adapter table should be left uninitialized.
|
|
Packit |
534379 |
During plugin cleanup, if a plugin has no `opae_plugin_finalize` entry
|
|
Packit |
534379 |
point in its adapter table, the plugin finalize step will be skipped, and
|
|
Packit |
534379 |
the plugin will be considered to have finalized successfully.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
In addition to `initialize` and `finalize`, an OPAE C API plugin has two
|
|
Packit |
534379 |
further optional entry points that relate to device enumeration. During
|
|
Packit |
534379 |
enumeration, when a plugin is being considered for a type of device, the
|
|
Packit |
534379 |
plugin may provide input on that decision by exporting an
|
|
Packit |
534379 |
`opae_plugin_supports_device` entry point in the adapter table:
|
|
Packit |
534379 |
|
|
Packit |
534379 |
```c
|
|
Packit |
534379 |
bool opae_plugin_supports_device(const char *device_type);
|
|
Packit |
534379 |
```
|
|
Packit |
534379 |
|
|
Packit |
534379 |
`opae_plugin_supports_device` returns true if the given device type is
|
|
Packit |
534379 |
supported and false if it is not. A false return value from
|
|
Packit |
534379 |
`opae_plugin_supports_device` causes device enumeration to skip the
|
|
Packit |
534379 |
plugin.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
Populating the `opae_plugin_supports_device` is done as:
|
|
Packit |
534379 |
|
|
Packit |
534379 |
```c
|
|
Packit |
534379 |
/* foo_plugin.c */
|
|
Packit |
534379 |
|
|
Packit |
534379 |
bool foo_plugin_supports_device(const char *device_type)
|
|
Packit |
534379 |
{
|
|
Packit |
534379 |
if (/* device_type is supported */)
|
|
Packit |
534379 |
return true;
|
|
Packit |
534379 |
|
|
Packit |
534379 |
...
|
|
Packit |
534379 |
|
|
Packit |
534379 |
return false;
|
|
Packit |
534379 |
}
|
|
Packit |
534379 |
|
|
Packit |
534379 |
int opae_plugin_configure(opae_api_adapter_table *table, const char *config)
|
|
Packit |
534379 |
{
|
|
Packit |
534379 |
...
|
|
Packit |
534379 |
|
|
Packit |
534379 |
adapter->supports_device =
|
|
Packit |
534379 |
dlsym(adapter->plugin.dl_handle, "foo_plugin_supports_device");
|
|
Packit |
534379 |
|
|
Packit |
534379 |
...
|
|
Packit |
534379 |
|
|
Packit |
534379 |
return 0;
|
|
Packit |
534379 |
}
|
|
Packit |
534379 |
```
|
|
Packit |
534379 |
|
|
Packit |
534379 |
```eval_rst
|
|
Packit |
534379 |
.. note::
|
|
Packit |
534379 |
The `opae_plugin_supports_device` mechanism serves as a placeholder only.
|
|
Packit |
534379 |
It is not implemented in the current version of the OPAE C API.
|
|
Packit |
534379 |
```
|
|
Packit |
534379 |
|
|
Packit |
534379 |
Similarly to determining whether a plugin supports a type of device, a plugin
|
|
Packit |
534379 |
may also answer questions about network host support by populating an
|
|
Packit |
534379 |
`opae_plugin_supports_host` entry point in the adapter table:
|
|
Packit |
534379 |
|
|
Packit |
534379 |
```c
|
|
Packit |
534379 |
bool opae_plugin_supports_host(const char *hostname);
|
|
Packit |
534379 |
```
|
|
Packit |
534379 |
|
|
Packit |
534379 |
`opae_plugin_supports_host` returns true if the given hostname is supported
|
|
Packit |
534379 |
and false if it is not. A false return value from `opae_plugin_supports_host`
|
|
Packit |
534379 |
causes device enumeration to skip the plugin.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
Populating the `opae_plugin_supports_host` is done as:
|
|
Packit |
534379 |
|
|
Packit |
534379 |
```c
|
|
Packit |
534379 |
/* foo_plugin.c */
|
|
Packit |
534379 |
|
|
Packit |
534379 |
bool foo_plugin_supports_host(const char *hostname)
|
|
Packit |
534379 |
{
|
|
Packit |
534379 |
if (/* hostname is supported */)
|
|
Packit |
534379 |
return true;
|
|
Packit |
534379 |
|
|
Packit |
534379 |
...
|
|
Packit |
534379 |
|
|
Packit |
534379 |
return false;
|
|
Packit |
534379 |
}
|
|
Packit |
534379 |
|
|
Packit |
534379 |
int opae_plugin_configure(opae_api_adapter_table *table, const char *config)
|
|
Packit |
534379 |
{
|
|
Packit |
534379 |
...
|
|
Packit |
534379 |
|
|
Packit |
534379 |
adapter->supports_host =
|
|
Packit |
534379 |
dlsym(adapter->plugin.dl_handle, "foo_plugin_supports_host");
|
|
Packit |
534379 |
|
|
Packit |
534379 |
...
|
|
Packit |
534379 |
|
|
Packit |
534379 |
return 0;
|
|
Packit |
534379 |
}
|
|
Packit |
534379 |
```
|
|
Packit |
534379 |
|
|
Packit |
534379 |
```eval_rst
|
|
Packit |
534379 |
.. note::
|
|
Packit |
534379 |
The `opae_plugin_supports_host` mechanism serves as a placeholder only.
|
|
Packit |
534379 |
It is not implemented in the current version of the OPAE C API.
|
|
Packit |
534379 |
```
|
|
Packit |
534379 |
|
|
Packit |
534379 |
## Plugin Construction ##
|
|
Packit |
534379 |
|
|
Packit |
534379 |
The steps required to implement an OPAE C API plugin, libfoo.so, are:
|
|
Packit |
534379 |
|
|
Packit |
534379 |
* Create foo\_plugin.c: implements `opae_plugin_configure`,
|
|
Packit |
534379 |
`opae_plugin_initialize`, `opae_plugin_finalize`, `opae_plugin_supports_device`,
|
|
Packit |
534379 |
and `opae_plugin_supports_host` as described in the previous sections.
|
|
Packit |
534379 |
* Create foo\_plugin.h: implements function prototypes for each of the
|
|
Packit |
534379 |
plugin-specific OPAE C APIs.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
```c
|
|
Packit |
534379 |
/* foo_plugin.h */
|
|
Packit |
534379 |
|
|
Packit |
534379 |
fpga_result foo_fpgaOpen(fpga_token token, fpga_handle *handle,
|
|
Packit |
534379 |
int flags);
|
|
Packit |
534379 |
|
|
Packit |
534379 |
fpga_result foo_fpgaClose(fpga_handle handle);
|
|
Packit |
534379 |
|
|
Packit |
534379 |
...
|
|
Packit |
534379 |
|
|
Packit |
534379 |
fpga_result foo_fpgaEnumerate(const fpga_properties *filters,
|
|
Packit |
534379 |
uint32_t num_filters, fpga_token *tokens,
|
|
Packit |
534379 |
uint32_t max_tokens,
|
|
Packit |
534379 |
uint32_t *num_matches);
|
|
Packit |
534379 |
...
|
|
Packit |
534379 |
```
|
|
Packit |
534379 |
|
|
Packit |
534379 |
* Create foo\_types.h: implements plugin-specific types for opaque data
|
|
Packit |
534379 |
structures.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
```c
|
|
Packit |
534379 |
/* foo_types.h */
|
|
Packit |
534379 |
|
|
Packit |
534379 |
struct _foo_token {
|
|
Packit |
534379 |
...
|
|
Packit |
534379 |
};
|
|
Packit |
534379 |
|
|
Packit |
534379 |
struct _foo_handle {
|
|
Packit |
534379 |
...
|
|
Packit |
534379 |
};
|
|
Packit |
534379 |
|
|
Packit |
534379 |
struct _foo_event_handle {
|
|
Packit |
534379 |
...
|
|
Packit |
534379 |
};
|
|
Packit |
534379 |
|
|
Packit |
534379 |
struct _foo_object {
|
|
Packit |
534379 |
...
|
|
Packit |
534379 |
};
|
|
Packit |
534379 |
```
|
|
Packit |
534379 |
|
|
Packit |
534379 |
* Create foo\_enum.c: implements `foo_fpgaEnumerate`,
|
|
Packit |
534379 |
`foo_fpgaCloneToken`, and `foo_fpgaDestroyToken`.
|
|
Packit |
534379 |
* Create foo\_open.c: implements `foo_fpgaOpen`.
|
|
Packit |
534379 |
* Create foo\_close.c: implements `foo_fpgaClose`.
|
|
Packit |
534379 |
* Create foo\_props.c: implements `foo_fpgaGetProperties`,
|
|
Packit |
534379 |
`foo_fpgaGetPropertiesFromHandle`, `foo_fpgaUpdateProperties`
|
|
Packit |
534379 |
* Create foo\_mmio.c: implements `foo_fpgaMapMMIO`, `foo_fpgaUnmapMMIO`
|
|
Packit |
534379 |
`foo_fpgaWriteMMIO64`, `foo_fpgaReadMMIO64`, `foo_fpgaWriteMMIO32`,
|
|
Packit |
534379 |
`foo_fpgaReadMMIO32`.
|
|
Packit |
534379 |
* Create foo\_buff.c: implements `foo_fpgaPrepareBuffer`,
|
|
Packit |
534379 |
`foo_fpgaReleaseBuffer`, `foo_fpgaGetIOAddress`.
|
|
Packit |
534379 |
* Create foo\_error.c: implements `foo_fpgaReadError`, `foo_fpgaClearError`,
|
|
Packit |
534379 |
`foo_fpgaClearAllErrors`, `foo_fpgaGetErrorInfo`.
|
|
Packit |
534379 |
* Create foo\_event.c: implements `foo_fpgaCreateEventHandle`,
|
|
Packit |
534379 |
`foo_fpgaDestroyEventHandle`, `foo_fpgaGetOSObjectFromEventHandle`,
|
|
Packit |
534379 |
`foo_fpgaRegisterEvent`, `foo_fpgaUnregisterEvent`.
|
|
Packit |
534379 |
* Create foo\_reconf.c: implements `foo_fpgaReconfigureSlot`.
|
|
Packit |
534379 |
* Create foo\_obj.c: implements `foo_fpgaTokenGetObject`,
|
|
Packit |
534379 |
`foo_fpgaHandleGetObject`, `foo_fpgaObjectGetObject`,
|
|
Packit |
534379 |
`foo_fpgaDestroyObject`, `foo_fpgaObjectGetSize`, `foo_fpgaObjectRead`,
|
|
Packit |
534379 |
`foo_fpgaObjectRead64`, `foo_fpgaObjectWrite64`.
|
|
Packit |
534379 |
* Create foo\_clk.c: implements `foo_fpgaSetUserClock`,
|
|
Packit |
534379 |
`foo_fpgaGetUserClock`.
|