aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorThanos Makatos <thanos.makatos@nutanix.com>2019-09-10 11:57:47 -0400
committerThanos <tmakatos@gmail.com>2019-09-20 14:45:21 +0100
commit5d8c80c9df26c8a91384531774eee220d1d7f6f8 (patch)
tree33c154bc3d3cf70416d40dce4db53d5d49456668 /lib
parent5d3393c21d4054394bdb042d13a3262afadb5e0e (diff)
downloadlibvfio-user-5d8c80c9df26c8a91384531774eee220d1d7f6f8.zip
libvfio-user-5d8c80c9df26c8a91384531774eee220d1d7f6f8.tar.gz
libvfio-user-5d8c80c9df26c8a91384531774eee220d1d7f6f8.tar.bz2
experimental support for Python bindings plus sample
This patch introduces experimental support for Python bindings, plus the GPIO sample in Python. There are many issues with extending Python (from module stuff to error handling), the most important one is the inability to modify the buf argument to the region access callbacks when a region is read from. For now I've resorted to returning the value since in libmuser we limit accesses to 8 bytes (maybe we should consider that for the C API as well?). Also, the fact that we don't pass the region being accessed as an argument to a region access callback results in clunky code (see the REGION_WRAP macro). Signed-off-by: Thanos Makatos <thanos.makatos@nutanix.com>
Diffstat (limited to 'lib')
-rw-r--r--lib/CMakeLists.txt13
-rw-r--r--lib/python_bindings.c216
-rwxr-xr-xlib/python_bindings_install.sh23
-rw-r--r--lib/setup.py13
4 files changed, 265 insertions, 0 deletions
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
index 92c1c27..3a0a811 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -48,3 +48,16 @@ install(TARGETS muser
install(DIRECTORY "caps"
DESTINATION ${MUSER_HEADERS_DIR}
FILES_MATCHING PATTERN "*.h")
+
+if (DEFINED ENV{PYTHON_BINDINGS})
+
+ add_custom_target(python_bindings_build ALL
+ COMMAND python setup.py build -b ${CMAKE_BINARY_DIR}
+ DEPENDS python_bindings.c setup.py
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/lib
+ SOURCES python_bindings.c setup.py)
+ # execute_process seems to only execute a single command, e.g. it can't
+ # handle two commands joined by &&
+ install(CODE "execute_process(COMMAND ${CMAKE_SOURCE_DIR}/lib/python_bindings_install.sh ${CMAKE_SOURCE_DIR})")
+
+endif()
diff --git a/lib/python_bindings.c b/lib/python_bindings.c
new file mode 100644
index 0000000..6b13707
--- /dev/null
+++ b/lib/python_bindings.c
@@ -0,0 +1,216 @@
+#include <Python.h>
+#include "muser.h"
+#include <assert.h>
+
+static PyObject *region_access_callbacks[LM_DEV_NUM_REGS] = { 0 };
+
+static int
+handle_read(char *dst, PyObject *result, int count)
+{
+ int i;
+
+ PyArg_Parse(result, "i", &i);
+ Py_DECREF(result);
+ memcpy(dst, &i, count);
+ return 0;
+}
+
+/*
+ * Function callback called by libmuser. We then call the Python function.
+ *
+ * FIXME need a way to provide private pointer.
+ */
+static ssize_t
+region_access_wrap(void *pvt, char * const buf, size_t count, loff_t offset,
+ const bool is_write, int region)
+{
+ PyObject *arglist;
+ PyObject *result = NULL;
+ uint64_t _buf = { 0 };
+
+ if (region_access_callbacks[region] == NULL)
+ {
+ fprintf(stderr, "FIXME no callback for region %d\n", region);
+ return -1;
+ }
+
+ if (is_write)
+ {
+ memcpy(&_buf, buf, count);
+ }
+
+ /* FIXME not sure about type of count and offset */
+ arglist = Py_BuildValue("IKIKi", pvt, _buf, count, offset, is_write);
+ if (arglist == NULL)
+ {
+ fprintf(stderr, "FIXME failed to build func args\n");
+ return -1;
+ }
+ result = PyEval_CallObject(region_access_callbacks[region], arglist);
+ Py_DECREF(arglist);
+ if (result == NULL)
+ {
+ fprintf(stderr, "FIXME failed to call fn for region %d\n", region);
+ return -1;
+ }
+ if (!is_write)
+ {
+ if (handle_read(buf, result, count))
+ {
+ return -1;
+ }
+ }
+ return count;
+}
+
+#define REGION_WRAP(region) \
+ static ssize_t \
+ r_ ## region ## _wrap(void *p, char * const b, size_t c, loff_t o, \
+ const bool w) \
+ { \
+ return region_access_wrap(p, b, c, o, w, region); \
+ }
+
+REGION_WRAP(0)
+REGION_WRAP(1)
+REGION_WRAP(2)
+REGION_WRAP(3)
+REGION_WRAP(4)
+REGION_WRAP(5)
+REGION_WRAP(6)
+REGION_WRAP(7)
+REGION_WRAP(8)
+
+static ssize_t (*region_access_wraps[LM_DEV_NUM_REGS])(void*, char * const, size_t, loff_t, const bool) = {
+ r_0_wrap,
+ r_1_wrap,
+ r_2_wrap,
+ r_3_wrap,
+ r_4_wrap,
+ r_5_wrap,
+ r_6_wrap,
+ r_7_wrap,
+ r_8_wrap};
+
+struct _region_info {
+ char *perm;
+ unsigned int size;
+ PyObject *fn;
+};
+
+static const struct _region_info _0_ri = { 0 };
+
+static PyObject *log_fn = NULL;
+static lm_log_lvl_t log_lvl = LM_ERR;
+
+static void _log_fn(void *pvt, const char *const msg)
+{
+ PyObject *arglist;
+ PyObject *result = NULL;
+
+ arglist = Py_BuildValue("(s)", msg);
+ result = PyEval_CallObject(log_fn, arglist);
+ Py_DECREF(arglist);
+ if (result != NULL)
+ {
+ Py_DECREF(result);
+ }
+}
+
+static PyObject *
+libmuser_run(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ static char *kwlist[] = {"vid", "did", "uuid", "log", "log_lvl",
+ "bar0", "bar1", "bar2", "bar3", "bar4", "bar5", "rom", "cfg", "vga",
+ "intx", "msi", "msix", "err", "req",
+ NULL};
+ int err;
+ lm_dev_info_t dev_info = { 0 };
+ int i;
+ struct _region_info _ri[LM_DEV_NUM_REGS] = { 0 };
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "HHs|Oi(sIO)(sIO)(sIO)(sIO)(sIO)(sIO)(sIO)(sIO)(sIO)IIIII", kwlist,
+ &dev_info.pci_info.id.vid,
+ &dev_info.pci_info.id.did,
+ &dev_info.uuid,
+ &log_fn, &log_lvl,
+ &_ri[0].perm, &_ri[0].size, &_ri[0].fn,
+ &_ri[1].perm, &_ri[1].size, &_ri[1].fn,
+ &_ri[2].perm, &_ri[2].size, &_ri[2].fn,
+ &_ri[3].perm, &_ri[3].size, &_ri[3].fn,
+ &_ri[4].perm, &_ri[4].size, &_ri[4].fn,
+ &_ri[5].perm, &_ri[5].size, &_ri[5].fn,
+ &_ri[6].perm, &_ri[6].size, &_ri[6].fn,
+ &_ri[7].perm, &_ri[7].size, &_ri[7].fn,
+ &_ri[8].perm, &_ri[8].size, &_ri[8].fn,
+ &dev_info.pci_info.irq_count[0],
+ &dev_info.pci_info.irq_count[1],
+ &dev_info.pci_info.irq_count[2],
+ &dev_info.pci_info.irq_count[3],
+ &dev_info.pci_info.irq_count[4]))
+ {
+ return NULL;
+ }
+
+ for (i = 0; i < LM_DEV_NUM_REGS; i++)
+ {
+ int j;
+ uint32_t flags = 0;
+
+ if (i == LM_DEV_CFG_REG_IDX && !memcmp(&_0_ri, &_ri[i], sizeof _0_ri))
+ {
+ continue;
+ }
+
+ if (_ri[i].perm != NULL)
+ {
+ for (j = 0; j < strlen(_ri[i].perm); j++)
+ {
+ if (_ri[i].perm[j] == 'r')
+ {
+ flags |= LM_REG_FLAG_READ;
+ }
+ else if (_ri[i].perm[j] == 'w')
+ {
+ flags |= LM_REG_FLAG_WRITE;
+ }
+ else
+ {
+ /* FIXME shouldn't print to stderr */
+ fprintf(stderr, "bad permission '%c'\n", _ri[i].perm[j]);
+ return NULL;
+ }
+ }
+ }
+ region_access_callbacks[i] = _ri[i].fn;
+ dev_info.pci_info.reg_info[i].flags = flags;
+ dev_info.pci_info.reg_info[i].size = _ri[i].size;
+ dev_info.pci_info.reg_info[i].fn = region_access_wraps[i];
+ }
+
+ if (log_fn != NULL)
+ {
+ if (!PyCallable_Check(log_fn))
+ {
+ return NULL;
+ }
+ dev_info.log = _log_fn;
+ dev_info.log_lvl = log_lvl;
+ }
+
+ err = lm_ctx_run(&dev_info);
+ return Py_BuildValue("i", err);
+}
+
+static PyMethodDef LibmuserMethods[] = {
+ {"run", (PyCFunction)libmuser_run, METH_VARARGS | METH_KEYWORDS, "runs a device"},
+ {NULL, NULL, 0, NULL}
+};
+
+PyMODINIT_FUNC
+initmuser(void)
+{
+ (void) Py_InitModule("muser", LibmuserMethods);
+}
+
+/* ex: set tabstop=4 shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/lib/python_bindings_install.sh b/lib/python_bindings_install.sh
new file mode 100755
index 0000000..82aae3f
--- /dev/null
+++ b/lib/python_bindings_install.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+# 'python setup.py install' expects to find a directory named 'build' with
+# subdirectories 'lib.linux-x86_64-2.7' and 'temp.linux-x86_64-2.7'. If not,
+# then it tries to build, however there's no way to tell it where to put the
+# binary files -- it puts them in the sources directory. These two directories
+# are created during the build phase (by 'python setup.py build') and the
+# binary files are explicitly stored under build/dbg.
+
+# current directory is build/dbg/, move back to build/
+cd ../
+
+# now sylmink dbg to build (parent dir is also build)
+ln -s dbg build
+
+# now symlink'ed dir build contains lib.linux-x86_64-2.7 and
+# temp.linux-x86_64-2.7
+
+# --skip-build seems necessary otherwise 'python setup.py install' to tries to
+# build because source files aren't found (we're under the build directory).
+# We can't simply cd into the source directory and run the install command there
+# because it expects to find the build/ directory there.
+python ${1}/lib/setup.py install --skip-build
diff --git a/lib/setup.py b/lib/setup.py
new file mode 100644
index 0000000..8d63e94
--- /dev/null
+++ b/lib/setup.py
@@ -0,0 +1,13 @@
+from distutils.core import setup, Extension
+
+module1 = Extension('muser',
+ sources = ['python_bindings.c'],
+ #library_dirs=['/usr/local/lib'],
+ libraries=['muser'],
+ #extra_compile_args=['-g', '-O0']
+)
+
+setup (name = 'PackageName',
+ version = '1.0',
+ description = 'This is a demo package',
+ ext_modules = [module1])