diff options
author | Bjørn Mork <bjorn@mork.no> | 2012-05-04 12:45:46 +0200 |
---|---|---|
committer | Bjørn Mork <bjorn@mork.no> | 2012-05-04 12:45:46 +0200 |
commit | f9ab5752988a4c8f0ed9135d0ba66c903f70f4d8 (patch) | |
tree | 361d54c88d30804494cf6a62f7ace9d28258537d /src | |
parent | 402fd4347f304ac83bd642c56f1e91b586431071 (diff) |
qcqmifs: initial version
Signed-off-by: Bjørn Mork <bjorn@mork.no>
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile | 14 | ||||
-rw-r--r-- | src/qcqmifs.c | 257 |
2 files changed, 270 insertions, 1 deletions
diff --git a/src/Makefile b/src/Makefile index 4443538..9d66584 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,4 +1,13 @@ -all: wwan_ctl qmi-prober +CC=gcc +CFLAGS_FUSE=$(shell pkg-config fuse --cflags) +LDLIBS_FUSE=$(shell pkg-config fuse --libs) +LDFLAGS=-Wall +BINARIES=wwan_ctl qcqmifs + +all: $(BINARIES) + +clean: + rm -rf *.o *.so *.lo *~ $(BINARIES) .libs wwan_ctl: wwan_ctl.c gcc -o wwan_ctl wwan_ctl.c @@ -6,3 +15,6 @@ wwan_ctl: wwan_ctl.c qmi-prober: qmi-prober.c $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) +qcqmifs: qcqmifs.c + $(CC) $(CFLAGS) $(CFLAGS_FUSE) $(LDFLAGS) -o $@ $^ $(LDLIBS) $(LDLIBS_FUSE) + diff --git a/src/qcqmifs.c b/src/qcqmifs.c new file mode 100644 index 0000000..c29988f --- /dev/null +++ b/src/qcqmifs.c @@ -0,0 +1,257 @@ +/* + qcqmifs - an example of how to use FUSE to create a shim between the + proprietary Qualcomm QMI SDK and the qmi_wwan driver in the mainline + Linux kernel. + + This code is heavily based on the FUSE "hello" example, which is + + Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> + + The Qualcomm QMI SDK interface expectations are pulled from the nicely + rewritten Qualcomm Gobi 2000/3000 driver by Elly Jones <ellyjones@google.com>. + That driver is + + Copyright (c) 2011, Code Aurora Forum. All rights reserved. + + The rest of the glue is + + Copyright (C) 2012 Bjørn Mork <bjorn@mork.no> + + This program can be distributed under the terms of the GNU GPLv2. + See the file COPYING. + + + Building it: + gcc -Wall `pkg-config fuse --cflags --libs` qcqmifs.c -o qcqmifs + + Running it (with the optional: + # ./qcqmifs /mnt/whatever -d -o default_permissions,allow_other + + Using it: + + - all existing /dev/cdc-wdmX devices will get a mirror + device under the chosen mountpoint named /mnt/whatever/qcqmiX + + - create a symlink from /dev/qcqmi or /dev/qcqmi0 to the wanted + mirror device + + Restrictions: + + - no dynamic device discovery. Program must be restarted to detect + new or removed devices + + - no automatic symlinking + + - /dev/qcqmiX where X > 0 is not supported by the SDK, so the symlink + names cannot always match real device names + + +*/ + +/* FUSE API version */ +#define FUSE_USE_VERSION 26 + +#include <fuse.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/types.h> +#include <dirent.h> + +static int qcqmi_getattr(const char *path, struct stat *stbuf) +{ + int res = 0; + int x; + char name[] = "/dev/cdc-wdmX"; + + fprintf(stderr, "%s: path=%s\n", __func__, path); + + if (strcmp(path, "/") == 0) { + memset(stbuf, 0, sizeof(struct stat)); + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 2; + } else { + if (strncmp("/qcqmi", path, 6)) + return -EINVAL; + + x = strtoul(path + 6, NULL, 10); + if (x > 9) + return -EINVAL; + + sprintf(name, "/dev/cdc-wdm%u", x); + res = stat(name, stbuf); + stbuf->st_mode = S_IFREG | 0664; + } + + return res; +} + +static int cdcwdm_filter(const struct dirent *d) +{ + return !strncmp("cdc-wdm", d->d_name, 7); +} + + +/* create a directory mirror of the current /dev/cdc-wdmX devices */ +static int qcqmi_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi) +{ + (void) offset; + (void) fi; + struct dirent **namelist; + int i, n, x; + char name[6]; + + if (strcmp(path, "/") != 0) + return -ENOENT; + + fprintf(stderr, "%s: path=%s\n", __func__, path); + + filler(buf, ".", NULL, 0); + filler(buf, "..", NULL, 0); + + n = scandir("/dev", &namelist, cdcwdm_filter, alphasort); + if (n < 0) + return n; + + for (i = 0; i < n; i++) { + x = strtoul(namelist[i]->d_name + 7, NULL, 10); + if (x < 10) { + sprintf(name, "qcqmi%u", x); + filler(buf, name, NULL, 0); + } + free(namelist[i]); + } + free(namelist); + return 0; +} + +struct cdcwdmdev { + char *name; + int fd; + int cid; +}; + +static int qcqmi_open(const char *path, struct fuse_file_info *fi) +{ + int fd, x; + char name[] = "/dev/cdc-wdmX"; + struct cdcwdmdev *handle; + + fprintf(stderr, "%s: path=%s\n", __func__, path); + + if (strncmp("/qcqmi", path, 6)) + return -EINVAL; + + x = strtoul(path + 6, NULL, 10); + if (x > 9) + return -EINVAL; + sprintf(name, "/dev/cdc-wdm%u", x); + + fd = open(name, fi->flags); + if (fd < 0) { + fprintf(stderr, "%s: open(%s) returned %d, errno=%d\n", __func__, name, fd, errno); + return -errno; + } + + handle = malloc(sizeof(struct cdcwdmdev)); + handle->name = strdup(name); + handle->fd = fd; + handle->cid = -1; /* invalid */ + + fi->nonseekable = 1; + fi->fh = (uint64_t)handle; + + return 0; +} + +static int qcqmi_release(const char *path, struct fuse_file_info *fi) +{ + struct cdcwdmdev *handle = (void *)fi->fh; + + fprintf(stderr, "%s: path=%s\n", __func__, path); + + fi->fh = (uint64_t)NULL; + close(handle->fd); + free(handle->name); + free(handle); + return 0; +} + + +static int qcqmi_read(const char *path, char *buf, size_t size, off_t offset, + struct fuse_file_info *fi) +{ + struct cdcwdmdev *handle = (void *)fi->fh; + + fprintf(stderr, "%s: path=%s, proxying to %s\n", __func__, path, handle->name); + return 0; +} + +static int qcqmi_write(const char *path, const char *buf, size_t size, off_t offset, + struct fuse_file_info *fi) +{ + struct cdcwdmdev *handle = (void *)fi->fh; + + fprintf(stderr, "%s: path=%s, proxying to %s\n", __func__, path, handle->name); + + return size; +} + +static int qcqmi_ioctl(const char *path, int cmd, void *arg, + struct fuse_file_info *fi, unsigned int flags, void *data) +{ + struct cdcwdmdev *handle = (void *)fi->fh; + + fprintf(stderr, "%s: path=%s, proxying to %s\n", __func__, path, handle->name); + return -EBADRQC; +} + +static int qcqmi_chmod(const char *path, mode_t mode) +{ + fprintf(stderr, "%s: path=%s, mode=%x\n", __func__, path, mode); + return 0; +} + + +static int qcqmi_chown(const char *path, uid_t uid, gid_t gid) +{ + fprintf(stderr, "%s: path=%s, uid=%u, gid=%u\n", __func__, path, uid, gid); + return 0; +} + +static int qcqmi_utimens(const char *path, const struct timespec tv[2]) +{ + fprintf(stderr, "%s: path=%s, tv[0].tv_sec=%lu, tv[1].tv_sec=%lu\n", __func__, path, tv[0].tv_sec, tv[1].tv_sec); + return 0; +} + +/* must implement truncate to be able to write to an arbitrary offset */ +static int qcqmi_truncate(const char *path, off_t offset) +{ + fprintf(stderr, "%s: path=%s, offset=%lx\n", __func__, path, offset); + return 0; +} + +static struct fuse_operations qcqmi_oper = { + .getattr = qcqmi_getattr, + .readdir = qcqmi_readdir, + .open = qcqmi_open, + .release = qcqmi_release, + .read = qcqmi_read, + .write = qcqmi_write, + .ioctl = qcqmi_ioctl, +// .chown = qcqmi_chown, +// .chmod = qcqmi_chmod, +// .utimens = qcqmi_utimens, + .truncate = qcqmi_truncate, +}; + +int main(int argc, char *argv[]) +{ + /* run file system */ + return fuse_main(argc, argv, &qcqmi_oper, NULL); +} |