Pergunta

I have a piece of software running on an embedded device (x86, recent linux). To ease development, use automated tests, etc., I want to run it on my host system. The code compiles just fine with some tweaks to the build-system. The next step would be creating "virtual devices".

The application do not use any kind of library but communicates with several devices directly by read, write and ioctl-calls. The devices represent custom hardware with a custom protocol. To create a virtual environement, I need to respond to this calls. One possible way would be:

  • create device drivers for every needed device (/dev/deviceA, /dev/deviceB, /dev/deviceC, ...)
  • create another device driver to communicate back to userspace (say, /dev/deviceSimulation)
  • all virtual devices will forward every call to /dev/deviceSimulation
  • another userspace application interacts with /dev/deviceSimulation and keeps track of the state of the simulation.

Is there an easier way to do this without the roundtrip trough the linux kernel?

Foi útil?

Solução

Coming back to this side project and answering my own question: Yes, the roundtrip trough the kernel have to be done. But there a library doing the grunt work: CUDA ( thanks, CL).

The documentation feels a bit scarce (or I'm looking at the wrong places) and the provided cuda example is filled with some other stuff fuse-way of handling parameters or buffer handling. So here another example (even more reduced, nothing usefull happens here...):

Device "driver" (cusetest.c):

#define FUSE_USE_VERSION 30
#define _FILE_OFFSET_BITS 64
#include <fuse/cuse_lowlevel.h>
#include <fuse/fuse_opt.h>

#include <stdio.h>

#define LOG(...) do { fprintf(stderr, __VA_ARGS__); puts(""); } while (0)

static void cusetest_open(fuse_req_t req, struct fuse_file_info *fi) {
    LOG("open");
    fuse_reply_open(req, fi);
}

static void cusetest_read(fuse_req_t req, size_t size, off_t off,
                         struct fuse_file_info *fi) {
    LOG("read");
    fuse_reply_buf(req, "Hello", size > 5 ? 5 : size);
}

static void cusetest_write(fuse_req_t req, const char *buf, size_t size,
                          off_t off, struct fuse_file_info *fi) {
    LOG("write (%u bytes)", size);
    fuse_reply_write(req, size);
}

static void cusetest_ioctl(fuse_req_t req, int cmd, void *arg,
                          struct fuse_file_info *fi, unsigned flags,
                          const void *in_buf, size_t in_bufsz, size_t out_bufsz) {
    LOG("ioctl %d: insize: %u outsize: %u", cmd, in_bufsz, out_bufsz);
    switch (cmd) {
    case 23:
        if (in_bufsz == 0) {
            struct iovec iov = { arg, sizeof(int) };
            fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
        } else {
            LOG("  got value: %d", *((int*)in_buf));
            fuse_reply_ioctl(req, 0, NULL, 0);
        }
        break;
    case 42:
        if (out_bufsz == 0) {
            struct iovec iov = { arg, sizeof(int) };
            fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
        } else {
            LOG("  write back value");
            int v = 42;
            fuse_reply_ioctl(req, 0, &v, sizeof(int));
        }
        break;
    }
}

static const struct cuse_lowlevel_ops cusetest_clop = {
        .open           = cusetest_open,
        .read           = cusetest_read,
        .write          = cusetest_write,
        .ioctl          = cusetest_ioctl,
};

int main(int argc, char** argv) {
    // -f: run in foreground, -d: debug ouput
    // Compile official example and use -h
    const char* cusearg[] = {"test", "-f", "-d"};
    const char* devarg[]  = {"DEVNAME=cusetest" };
    struct cuse_info ci;
    memset(&ci, 0x00, sizeof(ci));
    ci.flags = CUSE_UNRESTRICTED_IOCTL;
    ci.dev_info_argc=1;
    ci.dev_info_argv = devarg;

    return cuse_lowlevel_main(3, (char**) &cusearg, &ci, &cusetest_clop, NULL);
}

Compile and run: gcc -Wall -g -lfuse cusetest.c -o cusetest && sudo ./cusetest

Test program (testcusetest.c):

#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/ioctl.h>

int main() {
    int fd = open("/dev/cusetest", O_RDWR);

    const char* msg = "Fooooo";
    write(fd, msg, strlen(msg));

    int v = 63;
    ioctl(fd, 23, &v);
    fprintf(stderr, "value is now: %d\n", v);
    ioctl(fd, 42, &v);
    fprintf(stderr, "value is now: %d\n", v);
    close(fd);
    return 0;
}

Compile and run: gcc -Wall testcusetest.c -o testcusetest && ./testcusetest

Its much more to do but should be enough to get startet. A pitfall had been the CUSE_UNRESTRICTED_IOCTL. You need to reply with fuse_reply_ioctl_retry(), if the in- or outputbuffer have a zero length. The callback will be called again with the buffer filled in.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top