summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBjørn Mork <bjorn@mork.no>2012-05-04 12:45:46 +0200
committerBjørn Mork <bjorn@mork.no>2012-05-04 12:45:46 +0200
commitf9ab5752988a4c8f0ed9135d0ba66c903f70f4d8 (patch)
tree361d54c88d30804494cf6a62f7ace9d28258537d /src
parent402fd4347f304ac83bd642c56f1e91b586431071 (diff)
qcqmifs: initial version
Signed-off-by: Bjørn Mork <bjorn@mork.no>
Diffstat (limited to 'src')
-rw-r--r--src/Makefile14
-rw-r--r--src/qcqmifs.c257
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);
+}