From 518fdf09921bf9afef987fd17adb325d41afefcc Mon Sep 17 00:00:00 2001 From: Fady Date: Sat, 19 Sep 2026 23:58:47 +0200 Subject: [PATCH 1/5] hexagonfs: add create, write, truncate, unlink and rename for mapped directories The Snapdragon Sensor Core's registry service does not only read its registry: while it initialises it removes and rewrites entries, writes a temporary file next to the registry directory and renames it into place. Give hexagonfs the write side of its API for physically mapped directories (create/open for writing with O_TRUNC or O_APPEND, write, ftruncate, unlink, rename across mapped directories) and report S_IWUSR in stat so the framework sees writable files. Virtual directories and the read-only backends keep returning ENOSYS, so a tree that maps read-only system files (/usr/share/qcom) stays untouched: the writable directory is whatever the daemon is pointed at with -R. Link: https://github.com/linux-msm/hexagonrpc/issues/19 Signed-off-by: Fady --- hexagonrpcd/hexagonfs.c | 184 +++++++++++++++++++++++++++++++++ hexagonrpcd/hexagonfs.h | 16 +++ hexagonrpcd/hexagonfs_mapped.c | 153 ++++++++++++++++++++++++++- 3 files changed, 351 insertions(+), 2 deletions(-) diff --git a/hexagonrpcd/hexagonfs.c b/hexagonrpcd/hexagonfs.c index bd9f353..da0c95f 100644 --- a/hexagonrpcd/hexagonfs.c +++ b/hexagonrpcd/hexagonfs.c @@ -277,3 +277,187 @@ int hexagonfs_fstat(struct hexagonfs_fd **fds, int fileno, struct stat *stats) return fd->ops->stat(fd, stats); } + +/* + * Resolve the directory part of NAME for the operations that act on a name inside a directory (create, + * unlink, rename). Returns the file number of that directory and sets *BASE to the final component. When + * NAME has no directory part, DIRFD itself is returned and *OWNED stays false; otherwise the directory was + * opened here and the caller closes it. + */ +static int resolve_parent(struct hexagonfs_fd **fds, int rootfd, int dirfd, + const char *name, const char **base, bool *owned) +{ + const char *slash = strrchr(name, '/'); + char *dir; + int fileno; + + *owned = false; + + if (slash == NULL) { + *base = name; + return dirfd; + } + + *base = slash + 1; + + if (slash == name) + return rootfd; + + dir = strndup(name, slash - name); + if (dir == NULL) + return -ENOMEM; + + fileno = hexagonfs_openat(fds, rootfd, dirfd, dir); + free(dir); + if (fileno < 0) + return fileno; + + *owned = true; + + return fileno; +} + +int hexagonfs_create(struct hexagonfs_fd **fds, int rootfd, int dirfd, const char *name, int flags) +{ + struct hexagonfs_fd *dir, *fd; + const char *base; + bool owned; + int parent, ret; + + parent = resolve_parent(fds, rootfd, dirfd, name, &base, &owned); + if (parent < 0) + return parent; + + dir = fds[parent]; + if (dir == NULL) { + ret = -EBADF; + goto out; + } + + if (*base == '\0') { + ret = -EISDIR; + goto out; + } + + if (dir->ops->create == NULL) { + ret = -ENOSYS; + goto out; + } + + ret = dir->ops->create(dir, base, flags, &fd); + if (ret) + goto out; + + /* The file stands on its own: its directory may be closed before it. */ + fd->up = NULL; + + ret = allocate_file_number(fds, fd); + if (ret < 0) + destroy_file_descriptor(fd); + +out: + if (owned) + hexagonfs_close(fds, parent); + + return ret; +} + +ssize_t hexagonfs_write(struct hexagonfs_fd **fds, int fileno, size_t size, const void *ptr) +{ + struct hexagonfs_fd *fd; + + if (fileno < 0 || fileno >= HEXAGONFS_MAX_FD) + return -EBADF; + + fd = fds[fileno]; + if (fd == NULL) + return -EBADF; + + if (fd->ops->write == NULL) + return -ENOSYS; + + return fd->ops->write(fd, size, ptr); +} + +int hexagonfs_ftruncate(struct hexagonfs_fd **fds, int fileno, off_t len) +{ + struct hexagonfs_fd *fd; + + if (fileno < 0 || fileno >= HEXAGONFS_MAX_FD) + return -EBADF; + + fd = fds[fileno]; + if (fd == NULL) + return -EBADF; + + if (fd->ops->truncate == NULL) + return -ENOSYS; + + return fd->ops->truncate(fd, len); +} + +int hexagonfs_unlink(struct hexagonfs_fd **fds, int rootfd, int dirfd, const char *name) +{ + struct hexagonfs_fd *dir; + const char *base; + bool owned; + int parent, ret; + + parent = resolve_parent(fds, rootfd, dirfd, name, &base, &owned); + if (parent < 0) + return parent; + + dir = fds[parent]; + if (dir == NULL) { + ret = -EBADF; + } else if (*base == '\0') { + ret = -EISDIR; + } else if (dir->ops->unlink == NULL) { + ret = -ENOSYS; + } else { + ret = dir->ops->unlink(dir, base); + } + + if (owned) + hexagonfs_close(fds, parent); + + return ret; +} + +int hexagonfs_rename(struct hexagonfs_fd **fds, int rootfd, int dirfd, const char *name, const char *newname) +{ + struct hexagonfs_fd *dir, *newdir; + const char *base, *newbase; + bool owned, newowned = false; + int parent, newparent = -1, ret; + + parent = resolve_parent(fds, rootfd, dirfd, name, &base, &owned); + if (parent < 0) + return parent; + + newparent = resolve_parent(fds, rootfd, dirfd, newname, &newbase, &newowned); + if (newparent < 0) { + ret = newparent; + goto out; + } + + dir = fds[parent]; + newdir = fds[newparent]; + if (dir == NULL || newdir == NULL) { + ret = -EBADF; + } else if (*base == '\0' || *newbase == '\0') { + ret = -EISDIR; + } else if (dir->ops->rename == NULL) { + ret = -ENOSYS; + } else { + ret = dir->ops->rename(dir, base, newdir, newbase); + } + +out: + if (newowned) + hexagonfs_close(fds, newparent); + if (owned) + hexagonfs_close(fds, parent); + + return ret; +} diff --git a/hexagonrpcd/hexagonfs.h b/hexagonrpcd/hexagonfs.h index 7682604..b655f3d 100644 --- a/hexagonrpcd/hexagonfs.h +++ b/hexagonrpcd/hexagonfs.h @@ -42,6 +42,17 @@ struct hexagonfs_file_ops { ssize_t (*read)(struct hexagonfs_fd *fd, size_t size, void *ptr); int (*stat)(struct hexagonfs_fd *fd, struct stat *stats); int (*seek)(struct hexagonfs_fd *fd, off_t off, int whence); + /* Writable backends only (physical directories): create/open NAME for reading and writing below DIR + * (FLAGS: O_TRUNC, O_APPEND), write, truncate, unlink, rename within the same backend. */ + int (*create)(struct hexagonfs_fd *dir, + const char *name, + int flags, + struct hexagonfs_fd **out); + ssize_t (*write)(struct hexagonfs_fd *fd, size_t size, const void *ptr); + int (*truncate)(struct hexagonfs_fd *fd, off_t len); + int (*unlink)(struct hexagonfs_fd *dir, const char *name); + int (*rename)(struct hexagonfs_fd *dir, const char *name, + struct hexagonfs_fd *newdir, const char *newname); }; struct hexagonfs_dirent { @@ -77,5 +88,10 @@ int hexagonfs_fstat(struct hexagonfs_fd **fds, int fileno, struct stat *stats); int hexagonfs_lseek(struct hexagonfs_fd **fds, int fileno, off_t pos, int whence); int hexagonfs_readdir(struct hexagonfs_fd **fds, int fileno, size_t size, char *name); ssize_t hexagonfs_read(struct hexagonfs_fd **fds, int fileno, size_t size, void *ptr); +int hexagonfs_create(struct hexagonfs_fd **fds, int rootfd, int dirfd, const char *name, int flags); +ssize_t hexagonfs_write(struct hexagonfs_fd **fds, int fileno, size_t size, const void *ptr); +int hexagonfs_ftruncate(struct hexagonfs_fd **fds, int fileno, off_t len); +int hexagonfs_unlink(struct hexagonfs_fd **fds, int rootfd, int dirfd, const char *name); +int hexagonfs_rename(struct hexagonfs_fd **fds, int rootfd, int dirfd, const char *name, const char *newname); #endif diff --git a/hexagonrpcd/hexagonfs_mapped.c b/hexagonrpcd/hexagonfs_mapped.c index fd28c8c..9804527 100644 --- a/hexagonrpcd/hexagonfs_mapped.c +++ b/hexagonrpcd/hexagonfs_mapped.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -126,6 +127,116 @@ static int mapped_openat(struct hexagonfs_fd *dir, return ret; } +/* The physical directory behind a mapped file descriptor, or NULL for any other backend. */ +static struct mapped_ctx *mapped_ctx_of(struct hexagonfs_fd *fd) +{ + if (fd->ops != &hexagonfs_mapped_ops + && fd->ops != &hexagonfs_mapped_or_empty_ops + && fd->ops != &hexagonfs_mapped_sysfs_ops) + return NULL; + + return fd->data; +} + +static int mapped_create(struct hexagonfs_fd *dir, + const char *name, + int flags, + struct hexagonfs_fd **out) +{ + struct mapped_ctx *dir_ctx = mapped_ctx_of(dir); + struct hexagonfs_fd *fd; + struct mapped_ctx *ctx; + int ret; + + if (dir_ctx == NULL) + return -ENOSYS; + + ctx = malloc(sizeof(struct mapped_ctx)); + if (ctx == NULL) + return -ENOMEM; + + fd = malloc(sizeof(struct hexagonfs_fd)); + if (fd == NULL) { + ret = -ENOMEM; + goto err; + } + + ctx->fd = openat(dir_ctx->fd, name, + O_RDWR | O_CREAT | O_CLOEXEC | (flags & (O_TRUNC | O_APPEND)), + 0644); + if (ctx->fd == -1) { + ret = -errno; + goto err_free_fd; + } + + ctx->dir = NULL; + + fd->is_assigned = false; + fd->up = dir; + fd->ops = &hexagonfs_mapped_ops; + fd->data = ctx; + + *out = fd; + + return 0; + +err_free_fd: + free(fd); +err: + free(ctx); + return ret; +} + +static ssize_t mapped_write(struct hexagonfs_fd *fd, size_t size, const void *in) +{ + struct mapped_ctx *ctx = fd->data; + ssize_t ret; + + ret = write(ctx->fd, in, size); + if (ret < 0) + return -errno; + + return ret; +} + +static int mapped_truncate(struct hexagonfs_fd *fd, off_t len) +{ + struct mapped_ctx *ctx = fd->data; + + if (ftruncate(ctx->fd, len)) + return -errno; + + return 0; +} + +static int mapped_unlink(struct hexagonfs_fd *dir, const char *name) +{ + struct mapped_ctx *dir_ctx = mapped_ctx_of(dir); + + if (dir_ctx == NULL) + return -ENOSYS; + + if (unlinkat(dir_ctx->fd, name, 0)) + return -errno; + + return 0; +} + +static int mapped_rename(struct hexagonfs_fd *dir, const char *name, + struct hexagonfs_fd *newdir, const char *newname) +{ + struct mapped_ctx *dir_ctx = mapped_ctx_of(dir); + struct mapped_ctx *newdir_ctx = mapped_ctx_of(newdir); + + if (dir_ctx == NULL || newdir_ctx == NULL) + return -ENOSYS; + + if (renameat(dir_ctx->fd, name, newdir_ctx->fd, newname)) + return -errno; + + return 0; +} + static ssize_t mapped_read(struct hexagonfs_fd *fd, size_t size, void *out) { struct mapped_ctx *ctx = fd->data; @@ -199,11 +310,11 @@ static int mapped_stat(struct hexagonfs_fd *fd, struct stat *stats) if (phys.st_mode & S_IFDIR) { stats->st_mode = S_IFDIR - | S_IRUSR | S_IXUSR + | S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; } else { - stats->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; + stats->st_mode = S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; } stats->st_atim.tv_sec = phys.st_atim.tv_sec; @@ -264,6 +375,34 @@ static int mapped_or_empty_readdir(struct hexagonfs_fd *fd, } } +static int mapped_or_empty_create(struct hexagonfs_fd *dir, + const char *name, + int flags, + struct hexagonfs_fd **out) +{ + if (dir->data) + return mapped_create(dir, name, flags, out); + else + return -ENOENT; +} + +static int mapped_or_empty_unlink(struct hexagonfs_fd *dir, const char *name) +{ + if (dir->data) + return mapped_unlink(dir, name); + else + return -ENOENT; +} + +static int mapped_or_empty_rename(struct hexagonfs_fd *dir, const char *name, + struct hexagonfs_fd *newdir, const char *newname) +{ + if (dir->data) + return mapped_rename(dir, name, newdir, newname); + else + return -ENOENT; +} + static int mapped_or_empty_seek(struct hexagonfs_fd *fd, off_t off, int whence) { if (fd->data) @@ -330,6 +469,11 @@ struct hexagonfs_file_ops hexagonfs_mapped_ops = { .readdir = mapped_readdir, .seek = mapped_seek, .stat = mapped_stat, + .create = mapped_create, + .write = mapped_write, + .truncate = mapped_truncate, + .unlink = mapped_unlink, + .rename = mapped_rename, }; struct hexagonfs_file_ops hexagonfs_mapped_or_empty_ops = { @@ -340,6 +484,11 @@ struct hexagonfs_file_ops hexagonfs_mapped_or_empty_ops = { .readdir = mapped_or_empty_readdir, .seek = mapped_or_empty_seek, .stat = mapped_or_empty_stat, + .create = mapped_or_empty_create, + .write = mapped_write, + .truncate = mapped_truncate, + .unlink = mapped_or_empty_unlink, + .rename = mapped_or_empty_rename, }; struct hexagonfs_file_ops hexagonfs_mapped_sysfs_ops = { From 7543f3381729d529c631c36ac7ce25b9024528ec Mon Sep 17 00:00:00 2001 From: Fady Date: Sat, 19 Sep 2026 23:58:47 +0200 Subject: [PATCH 2/5] apps_std: serve fopen, fwrite, fsync, fremove, ftrunc and frename Implement the apps_std methods the sensor framework's registry initialisation calls (ids as in quic/fastrpc inc/apps_std.h: fopen 0, fwrite 5, fsync 23, fremove 24, ftrunc 32, frename 33; ids above 30 travel in the first primitive word), open w/a/+ modes read-write, and let fopen_with_env open an absolute name whatever search variable the DSP names. Refusing the first write (fremove, then fopen of the DIR marker for writing) made the X1E80100 firmware assert in sns_registry_sensor.c and take the whole ADSP down. Unlike the stubs proposed in pull request 21, the writes reach the files, so what the framework persists (its parsed registry, calibration) is there at the next start. Link: https://github.com/linux-msm/hexagonrpc/issues/19 Link: https://github.com/linux-msm/hexagonrpc/pull/21 Signed-off-by: Fady --- hexagonrpcd/apps_std.c | 299 +++++++++++++++++++++++-------- hexagonrpcd/interface/apps_std.c | 79 ++++++++ hexagonrpcd/interface/apps_std.h | 6 + 3 files changed, 311 insertions(+), 73 deletions(-) diff --git a/hexagonrpcd/apps_std.c b/hexagonrpcd/apps_std.c index 7172bf0..c1a8cb8 100644 --- a/hexagonrpcd/apps_std.c +++ b/hexagonrpcd/apps_std.c @@ -20,6 +20,8 @@ */ #include +#include +#include #include #include #include @@ -150,13 +152,210 @@ static uint32_t apps_std_fseek(void *data, return 0; } +/* + * Open NAME below DIRFD the way fopen(3) would for MODE: read-only for "r", + * otherwise (w, a, +) read-write, created if absent, truncated for "w", + * appended for "a". The sensor framework of the Snapdragon Sensor Core + * writes into its registry directory while it initialises and aborts the + * whole DSP when that is refused. + */ +static int apps_std_open_mode(struct apps_std_ctx *ctx, int dirfd, + const char *name, const char *mode) +{ + const char *m; + bool writing = false; + int flags = 0; + + for (m = mode; *m != '\0'; m++) { + switch (*m) { + case 'w': + writing = true; + flags |= O_TRUNC; + break; + case 'a': + writing = true; + flags |= O_APPEND; + break; + case '+': + writing = true; + break; + default: + break; + } + } + + if (!writing) + return hexagonfs_openat(ctx->fds, ctx->rootfd, dirfd, name); + + return hexagonfs_create(ctx->fds, ctx->rootfd, dirfd, name, flags); +} + +static uint32_t apps_std_fopen(void *data, + const struct fastrpc_io_buffer *inbufs, + struct fastrpc_io_buffer *outbufs) +{ + struct apps_std_ctx *ctx = data; + uint32_t *out = outbufs[0].p; + const char *name = inbufs[1].p; + const char *mode = inbufs[2].p; + int dirfd, fd; + + // The name and mode must be NULL-terminated + if (name[inbufs[1].s - 1] != 0 || mode[inbufs[2].s - 1] != 0) + return AEE_EBADPARM; + + dirfd = name[0] == '/' ? ctx->rootfd : ctx->adsp_library_dirfd; + if (dirfd < 0) { + fprintf(stderr, "Could not open %s: no search directory\n", name); + return AEE_EFAILED; + } + + fd = apps_std_open_mode(ctx, dirfd, name, mode); + if (fd < 0) { + fprintf(stderr, "Could not open %s (%s): %s\n", + name, mode, strerror(-fd)); + return AEE_EFAILED; + } + +#ifdef HEXAGONRPC_VERBOSE + printf("open(%s, %s) -> %d\n", name, mode, fd); +#endif + + *out = fd; + + return 0; +} + +static uint32_t apps_std_fwrite(void *data, + const struct fastrpc_io_buffer *inbufs, + struct fastrpc_io_buffer *outbufs) +{ + struct apps_std_ctx *ctx = data; + const struct { + uint32_t fd; + uint32_t buf_size; + } *first_in = inbufs[0].p; + struct { + uint32_t written; + uint32_t is_eof; + } *first_out = outbufs[0].p; + ssize_t ret; + + ret = hexagonfs_write(ctx->fds, first_in->fd, inbufs[1].s, inbufs[1].p); + if (ret < 0) { + fprintf(stderr, "Could not write file: %s\n", strerror(-ret)); + return AEE_EFAILED; + } + +#ifdef HEXAGONRPC_VERBOSE + printf("write(%u, %u) -> %ld\n", first_in->fd, first_in->buf_size, ret); +#endif + + first_out->written = ret; + first_out->is_eof = 0; + + return 0; +} + +/* + * Writes are synchronous here and land in the page cache; nothing to wait + * for from the DSP's point of view. + */ +static uint32_t apps_std_fsync(void *data, + const struct fastrpc_io_buffer *inbufs, + struct fastrpc_io_buffer *outbufs) +{ +#ifdef HEXAGONRPC_VERBOSE + const uint32_t *fd = inbufs[0].p; + + printf("ignore fsync(%u)\n", *fd); +#endif + + return 0; +} + +static uint32_t apps_std_fremove(void *data, + const struct fastrpc_io_buffer *inbufs, + struct fastrpc_io_buffer *outbufs) +{ + struct apps_std_ctx *ctx = data; + const char *name = inbufs[1].p; + int ret; + + // The name must be NULL-terminated + if (name[inbufs[1].s - 1] != 0) + return AEE_EBADPARM; + + ret = hexagonfs_unlink(ctx->fds, ctx->rootfd, ctx->rootfd, name); + +#ifdef HEXAGONRPC_VERBOSE + printf("remove(%s) -> %d\n", name, ret); +#endif + + if (ret) { + fprintf(stderr, "Could not remove %s: %s\n", name, strerror(-ret)); + return AEE_EFAILED; + } + + return 0; +} + +/* Extended method (id > 30): the first word of the primary buffer is the method id. */ +static uint32_t apps_std_ftrunc(void *data, + const struct fastrpc_io_buffer *inbufs, + struct fastrpc_io_buffer *outbufs) +{ + struct apps_std_ctx *ctx = data; + const uint32_t *words = inbufs[0].p; + int ret; + + ret = hexagonfs_ftruncate(ctx->fds, words[1], words[2]); + +#ifdef HEXAGONRPC_VERBOSE + printf("ftruncate(%u, %u) -> %d\n", words[1], words[2], ret); +#endif + + if (ret) { + fprintf(stderr, "Could not truncate: %s\n", strerror(-ret)); + return AEE_EFAILED; + } + + return 0; +} + +static uint32_t apps_std_frename(void *data, + const struct fastrpc_io_buffer *inbufs, + struct fastrpc_io_buffer *outbufs) +{ + struct apps_std_ctx *ctx = data; + const char *oldname = inbufs[1].p; + const char *newname = inbufs[2].p; + int ret; + + // Both names must be NULL-terminated + if (oldname[inbufs[1].s - 1] != 0 || newname[inbufs[2].s - 1] != 0) + return AEE_EBADPARM; + + ret = hexagonfs_rename(ctx->fds, ctx->rootfd, ctx->rootfd, oldname, newname); + +#ifdef HEXAGONRPC_VERBOSE + printf("rename(%s, %s) -> %d\n", oldname, newname, ret); +#endif + + if (ret) { + fprintf(stderr, "Could not rename %s: %s\n", oldname, strerror(-ret)); + return AEE_EFAILED; + } + + return 0; +} + static uint32_t apps_std_fopen_with_env(void *data, const struct fastrpc_io_buffer *inbufs, struct fastrpc_io_buffer *outbufs) { struct apps_std_ctx *ctx = data; uint32_t *out = outbufs[0].p; - char rw_mode; int dirfd, fd; // The name and environment variable must also be NULL-terminated @@ -165,17 +364,14 @@ static uint32_t apps_std_fopen_with_env(void *data, || ((const char *) inbufs[4].p)[inbufs[4].s - 1] != 0) return AEE_EBADPARM; - rw_mode = ((const char *) inbufs[4].p)[0]; - if (rw_mode == 'w' || rw_mode == 'a') { - fprintf(stderr, "Tried to open %s for writing\n", - (const char *) inbufs[3].p); - return AEE_EUNSUPPORTED; - } - if (!strcmp(inbufs[1].p, "ADSP_LIBRARY_PATH")) { dirfd = ctx->adsp_library_dirfd; } else if (!strcmp(inbufs[1].p, "ADSP_AVS_CFG_PATH")) { dirfd = ctx->adsp_avs_cfg_dirfd; + } else if (((const char *) inbufs[3].p)[0] == '/') { + /* An absolute name needs no search directory (the sensor + * framework passes its own variable names with them). */ + dirfd = ctx->rootfd; } else { fprintf(stderr, "Unknown search directory %s\n", (const char *) inbufs[1].p); @@ -188,18 +384,19 @@ static uint32_t apps_std_fopen_with_env(void *data, return AEE_EFAILED; } - fd = hexagonfs_openat(ctx->fds, ctx->rootfd, dirfd, inbufs[3].p); + fd = apps_std_open_mode(ctx, dirfd, inbufs[3].p, inbufs[4].p); if (fd < 0) { - fprintf(stderr, "Could not open %s: %s\n", + fprintf(stderr, "Could not open %s (%s): %s\n", (const char *) inbufs[3].p, - strerror(errno)); + (const char *) inbufs[4].p, + strerror(-fd)); return AEE_EFAILED; } #ifdef HEXAGONRPC_VERBOSE - printf("openat($%s, %s, %c) -> %d\n", (const char *) inbufs[1].p, + printf("openat($%s, %s, %s) -> %d\n", (const char *) inbufs[1].p, (const char *) inbufs[3].p, - rw_mode, + (const char *) inbufs[4].p, fd); #endif @@ -406,69 +603,25 @@ void fastrpc_apps_std_deinit(struct fastrpc_interface *iface) } static const struct fastrpc_function_impl apps_std_procs[] = { - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { - .def = &apps_std_fflush_def, - .impl = apps_std_fflush, - }, - { - .def = &apps_std_fclose_def, - .impl = apps_std_fclose, - }, - { - .def = &apps_std_fread_def, - .impl = apps_std_fread, - }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { - .def = &apps_std_fseek_def, - .impl = apps_std_fseek, - }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { - .def = &apps_std_fopen_with_env_def, - .impl = apps_std_fopen_with_env, - }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { - .def = &apps_std_opendir_def, - .impl = apps_std_opendir, - }, - { - .def = &apps_std_closedir_def, - .impl = apps_std_closedir, - }, - { - .def = &apps_std_readdir_def, - .impl = apps_std_readdir, - }, - { .def = NULL, .impl = NULL, }, - { .def = NULL, .impl = NULL, }, - { - .def = &apps_std_stat_def, - .impl = apps_std_stat, - }, + [0] = { .def = &apps_std_fopen_def, .impl = apps_std_fopen, }, + [2] = { .def = &apps_std_fflush_def, .impl = apps_std_fflush, }, + [3] = { .def = &apps_std_fclose_def, .impl = apps_std_fclose, }, + [4] = { .def = &apps_std_fread_def, .impl = apps_std_fread, }, + [5] = { .def = &apps_std_fwrite_def, .impl = apps_std_fwrite, }, + [9] = { .def = &apps_std_fseek_def, .impl = apps_std_fseek, }, + [19] = { .def = &apps_std_fopen_with_env_def, .impl = apps_std_fopen_with_env, }, + [23] = { .def = &apps_std_fsync_def, .impl = apps_std_fsync, }, + [24] = { .def = &apps_std_fremove_def, .impl = apps_std_fremove, }, + [26] = { .def = &apps_std_opendir_def, .impl = apps_std_opendir, }, + [27] = { .def = &apps_std_closedir_def, .impl = apps_std_closedir, }, + [28] = { .def = &apps_std_readdir_def, .impl = apps_std_readdir, }, + [31] = { .def = &apps_std_stat_def, .impl = apps_std_stat, }, + [32] = { .def = &apps_std_ftrunc_def, .impl = apps_std_ftrunc, }, + [33] = { .def = &apps_std_frename_def, .impl = apps_std_frename, }, }; const struct fastrpc_interface apps_std_interface = { .name = "apps_std", - .n_procs = 32, + .n_procs = 34, .procs = apps_std_procs, }; diff --git a/hexagonrpcd/interface/apps_std.c b/hexagonrpcd/interface/apps_std.c index 67b3ace..a2b3910 100644 --- a/hexagonrpcd/interface/apps_std.c +++ b/hexagonrpcd/interface/apps_std.c @@ -22,6 +22,20 @@ #include #include +static struct hrpc_arg_def_interp4 apps_std_fopen_args[] = { + { HRPC_ARG_BLOB_SEQ, sizeof(char) }, + { HRPC_ARG_BLOB_SEQ, sizeof(char) }, + { HRPC_ARG_OUT_BLOB, sizeof(uint32_t) }, +}; + +const struct hrpc_method_def_interp4 apps_std_fopen_def = { + .msg_id = 0, + .n_args = HRPC_ARRAY_SIZE(apps_std_fopen_args), + .args = apps_std_fopen_args, + .n_inner_types = 0, + .inner_types = NULL, +}; + static struct hrpc_arg_def_interp4 apps_std_freopen_args[] = { { HRPC_ARG_WORD, sizeof(uint32_t) }, { HRPC_ARG_OUT_BLOB, sizeof(uint32_t) }, @@ -75,6 +89,21 @@ const struct hrpc_method_def_interp4 apps_std_fread_def = { .inner_types = NULL, }; +static struct hrpc_arg_def_interp4 apps_std_fwrite_args[] = { + { HRPC_ARG_WORD, sizeof(uint32_t) }, + { HRPC_ARG_BLOB_SEQ, sizeof(char) }, + { HRPC_ARG_OUT_BLOB, sizeof(uint32_t) }, + { HRPC_ARG_OUT_BLOB, sizeof(uint32_t) }, +}; + +const struct hrpc_method_def_interp4 apps_std_fwrite_def = { + .msg_id = 5, + .n_args = HRPC_ARRAY_SIZE(apps_std_fwrite_args), + .args = apps_std_fwrite_args, + .n_inner_types = 0, + .inner_types = NULL, +}; + static struct hrpc_arg_def_interp4 apps_std_fseek_args[] = { { HRPC_ARG_WORD, sizeof(uint32_t) }, { HRPC_ARG_WORD, sizeof(uint32_t) }, @@ -105,6 +134,30 @@ const struct hrpc_method_def_interp4 apps_std_fopen_with_env_def = { .inner_types = NULL, }; +static struct hrpc_arg_def_interp4 apps_std_fsync_args[] = { + { HRPC_ARG_WORD, sizeof(uint32_t) }, +}; + +const struct hrpc_method_def_interp4 apps_std_fsync_def = { + .msg_id = 23, + .n_args = HRPC_ARRAY_SIZE(apps_std_fsync_args), + .args = apps_std_fsync_args, + .n_inner_types = 0, + .inner_types = NULL, +}; + +static struct hrpc_arg_def_interp4 apps_std_fremove_args[] = { + { HRPC_ARG_BLOB_SEQ, sizeof(char) }, +}; + +const struct hrpc_method_def_interp4 apps_std_fremove_def = { + .msg_id = 24, + .n_args = HRPC_ARRAY_SIZE(apps_std_fremove_args), + .args = apps_std_fremove_args, + .n_inner_types = 0, + .inner_types = NULL, +}; + static struct hrpc_arg_def_interp4 apps_std_opendir_args[] = { { HRPC_ARG_BLOB_SEQ, sizeof(char) }, { HRPC_ARG_OUT_BLOB, sizeof(uint64_t) }, @@ -182,3 +235,29 @@ const struct hrpc_method_def_interp4 apps_std_stat_def = { .n_inner_types = 0, .inner_types = NULL, }; + +static struct hrpc_arg_def_interp4 apps_std_ftrunc_args[] = { + { HRPC_ARG_WORD, sizeof(uint32_t) }, + { HRPC_ARG_WORD, sizeof(uint32_t) }, +}; + +const struct hrpc_method_def_interp4 apps_std_ftrunc_def = { + .msg_id = 32, + .n_args = HRPC_ARRAY_SIZE(apps_std_ftrunc_args), + .args = apps_std_ftrunc_args, + .n_inner_types = 0, + .inner_types = NULL, +}; + +static struct hrpc_arg_def_interp4 apps_std_frename_args[] = { + { HRPC_ARG_BLOB_SEQ, sizeof(char) }, + { HRPC_ARG_BLOB_SEQ, sizeof(char) }, +}; + +const struct hrpc_method_def_interp4 apps_std_frename_def = { + .msg_id = 33, + .n_args = HRPC_ARRAY_SIZE(apps_std_frename_args), + .args = apps_std_frename_args, + .n_inner_types = 0, + .inner_types = NULL, +}; diff --git a/hexagonrpcd/interface/apps_std.h b/hexagonrpcd/interface/apps_std.h index b80a1ca..7f2ac06 100644 --- a/hexagonrpcd/interface/apps_std.h +++ b/hexagonrpcd/interface/apps_std.h @@ -24,16 +24,22 @@ #include +extern struct hrpc_method_def_interp4 apps_std_fopen_def; extern struct hrpc_method_def_interp4 apps_std_freopen_def; extern struct hrpc_method_def_interp4 apps_std_fflush_def; extern struct hrpc_method_def_interp4 apps_std_fclose_def; extern struct hrpc_method_def_interp4 apps_std_fread_def; +extern struct hrpc_method_def_interp4 apps_std_fwrite_def; extern struct hrpc_method_def_interp4 apps_std_fseek_def; extern struct hrpc_method_def_interp4 apps_std_fopen_with_env_def; +extern struct hrpc_method_def_interp4 apps_std_fsync_def; +extern struct hrpc_method_def_interp4 apps_std_fremove_def; extern struct hrpc_method_def_interp4 apps_std_opendir_def; extern struct hrpc_method_def_interp4 apps_std_closedir_def; extern struct hrpc_method_def_interp4 apps_std_readdir_def; extern struct hrpc_method_def_interp4 apps_std_mkdir_def; extern struct hrpc_method_def_interp4 apps_std_stat_def; +extern struct hrpc_method_def_interp4 apps_std_ftrunc_def; +extern struct hrpc_method_def_interp4 apps_std_frename_def; #endif /* INTERFACE_APPS_STD_H */ From f0dfd5095d3b7defc3b368f227337bfc6d6aef42 Mon Sep 17 00:00:00 2001 From: Fady Date: Sat, 19 Sep 2026 23:58:47 +0200 Subject: [PATCH 3/5] listener: answer unsupported requests and fetch input buffers longer than 256 bytes A request the server cannot serve is answered with AEE_EUNSUPPORTED/AEE_EBADPARM and empty output buffers instead of ending the session, so the DSP sees a failed call rather than a vanished listener. Requests whose input buffers exceed the 256 bytes adsp_listener_next2 inlines (the framework writing its secure database in one call) are completed with adsp_listener_get_in_bufs2 from offset 256, as Qualcomm's listener_android.c does; the method's definition follows quic/fastrpc inc/adsp_listener.h (index 5). Link: https://github.com/linux-msm/hexagonrpc/issues/19 Signed-off-by: Fady --- hexagonrpcd/interface/adsp_listener.c | 16 ++++ hexagonrpcd/interface/adsp_listener.h | 18 ++++ hexagonrpcd/listener.c | 126 +++++++++++++++++++++----- 3 files changed, 136 insertions(+), 24 deletions(-) diff --git a/hexagonrpcd/interface/adsp_listener.c b/hexagonrpcd/interface/adsp_listener.c index d2197df..507efdc 100644 --- a/hexagonrpcd/interface/adsp_listener.c +++ b/hexagonrpcd/interface/adsp_listener.c @@ -48,3 +48,19 @@ const struct hrpc_method_def_interp4 adsp_listener_next2_def = { .n_inner_types = 0, .inner_types = NULL, }; + +/* get_in_bufs2(ctx, offset, rout bufs, rout bufsLenReq), quic/fastrpc inc/adsp_listener.h */ +struct hrpc_arg_def_interp4 adsp_listener_get_in_bufs2_args[] = { + { HRPC_ARG_WORD, sizeof(uint32_t) }, + { HRPC_ARG_WORD, sizeof(uint32_t) }, + { HRPC_ARG_OUT_BLOB, sizeof(uint32_t) }, + { HRPC_ARG_OUT_BLOB_SEQ, sizeof(char) }, +}; + +const struct hrpc_method_def_interp4 adsp_listener_get_in_bufs2_def = { + .msg_id = 5, + .n_args = HRPC_ARRAY_SIZE(adsp_listener_get_in_bufs2_args), + .args = adsp_listener_get_in_bufs2_args, + .n_inner_types = 0, + .inner_types = NULL, +}; diff --git a/hexagonrpcd/interface/adsp_listener.h b/hexagonrpcd/interface/adsp_listener.h index 362d775..d467bb8 100644 --- a/hexagonrpcd/interface/adsp_listener.h +++ b/hexagonrpcd/interface/adsp_listener.h @@ -51,4 +51,22 @@ static inline int adsp_listener_next2(int fd, inbufs_size, (void *) inbufs); } +/* + * Fetches the part of a request's input buffers that next2 could not + * inline: OFFSET bytes in, up to INBUFS_SIZE bytes into INBUFS + * (quic/fastrpc inc/adsp_listener.h: get_in_bufs2(ctx, offset, bufs, + * bufsLen, bufsLenReq)). + */ +extern struct hrpc_method_def_interp4 adsp_listener_get_in_bufs2_def; +static inline int adsp_listener_get_in_bufs2(int fd, + uint32_t rctx, + uint32_t offset, + uint32_t *inbufs_len, + uint32_t inbufs_size, char *inbufs) +{ + return hexagonrpc(&adsp_listener_get_in_bufs2_def, fd, ADSP_LISTENER, + rctx, offset, inbufs_len, + inbufs_size, (void *) inbufs); +} + #endif /* INTERFACE_ADSP_LISTENER_H */ diff --git a/hexagonrpcd/listener.c b/hexagonrpcd/listener.c index 3414974..8912849 100644 --- a/hexagonrpcd/listener.c +++ b/hexagonrpcd/listener.c @@ -25,6 +25,8 @@ #include #include #include +#include +#include #include "interface/adsp_listener.h" #include "iobuffer.h" @@ -416,6 +418,55 @@ static struct fastrpc_io_buffer *alloc_outbufs4(const struct hrpc_method_def_int return NULL; } +/* + * adsp_listener_next2 inlines at most this many bytes of a request's input + * buffers and reports the full length. A longer request (the sensor + * framework writing a file of tens of kilobytes) is completed with + * adsp_listener_get_in_bufs2 from that offset, as Qualcomm's listener does. + */ +#define LISTENER_INLINE_INBUFS 256 +#define LISTENER_MAX_INBUFS (16 * 1024 * 1024) + +static int fetch_large_inbufs(int fd, uint32_t rctx, uint32_t inbufs_len, + const char *head, char **out) +{ + uint32_t rest = inbufs_len - LISTENER_INLINE_INBUFS; + uint32_t got = 0; + char *buf; + int ret; + + if (inbufs_len > LISTENER_MAX_INBUFS) { + fprintf(stderr, "Refusing %" PRIu32 " bytes of input buffers\n", + inbufs_len); + return -1; + } + + buf = malloc(inbufs_len); + if (buf == NULL) { + perror("Could not allocate large input buffers"); + return -1; + } + + memcpy(buf, head, LISTENER_INLINE_INBUFS); + + ret = adsp_listener_get_in_bufs2(fd, rctx, LISTENER_INLINE_INBUFS, + &got, rest, + buf + LISTENER_INLINE_INBUFS); + if (ret) { + if (ret == -1) + perror("Could not fetch large input buffers"); + else + fprintf(stderr, "Could not fetch large input buffers: %d\n", ret); + + free(buf); + return -1; + } + + *out = buf; + + return 0; +} + static int return_for_next_invoke(int fd, uint32_t result, uint32_t *rctx, @@ -425,8 +476,10 @@ static int return_for_next_invoke(int fd, struct fastrpc_io_buffer **decoded) { struct fastrpc_decoder_context *ctx; - char inbufs[256]; + char inbufs[LISTENER_INLINE_INBUFS]; char *outbufs = NULL; + char *large = NULL; + const char *src; uint32_t inbufs_len; uint32_t outbufs_len; int ret; @@ -447,7 +500,7 @@ static int return_for_next_invoke(int fd, *rctx, result, outbufs_len, outbufs, rctx, handle, sc, - &inbufs_len, 256, inbufs); + &inbufs_len, LISTENER_INLINE_INBUFS, inbufs); if (ret) { if (ret == -1) perror("Could not fetch next FastRPC message"); @@ -457,38 +510,70 @@ static int return_for_next_invoke(int fd, goto err_free_outbufs; } - if (inbufs_len > 256) { - fprintf(stderr, "Large (>256B) input buffers aren't implemented\n"); - ret = -1; - goto err_free_outbufs; + src = inbufs; + if (inbufs_len > LISTENER_INLINE_INBUFS) { + ret = fetch_large_inbufs(fd, *rctx, inbufs_len, inbufs, &large); + if (ret) + goto err_free_outbufs; + + src = large; } ctx = inbuf_decode_start(*sc); if (!ctx) { perror("Could not start decoding"); ret = -1; - goto err_free_outbufs; + goto err_free_large; } - ret = inbuf_decode(ctx, inbufs_len, inbufs); + ret = inbuf_decode(ctx, inbufs_len, src); if (ret) { perror("Could not decode"); - goto err_free_outbufs; + goto err_free_large; } if (!inbuf_decode_is_complete(ctx)) { fprintf(stderr, "Expected more input buffers\n"); ret = -1; - goto err_free_outbufs; + goto err_free_large; } *decoded = inbuf_decode_finish(ctx); +err_free_large: + free(large); err_free_outbufs: free(outbufs); return ret; } +/* + * A request this server cannot serve is answered with an error and empty + * output buffers, so that the DSP sees a failed call rather than a vanished + * listener; the DSP-side sensor framework retries or continues on an error + * but aborts without the answer. Returns 1 only when even that answer cannot + * be built. + */ +static int decline_request(uint32_t sc, uint32_t error, uint32_t *result, + struct fastrpc_io_buffer **returned) +{ + uint8_t n_outbufs = REMOTE_SCALARS_OUTBUFS(sc); + + *result = error; + *returned = NULL; + + if (n_outbufs == 0) + return 0; + + *returned = calloc(n_outbufs, sizeof(**returned)); + if (*returned == NULL) { + perror("Could not allocate empty output buffers"); + return 1; + } + + return 0; +} + static int invoke_requested_procedure(size_t n_ifaces, struct fastrpc_interface **ifaces, uint32_t handle, @@ -508,51 +593,44 @@ static int invoke_requested_procedure(size_t n_ifaces, method = *(const uint32_t *) decoded[0].p; } else { fprintf(stderr, "Expected extended method ID, but got none\n"); - *result = AEE_EBADPARM; - return 1; + return decline_request(sc, AEE_EBADPARM, result, returned); } if (sc & 0xff) { fprintf(stderr, "Handles are not supported, but got %u in, %u out\n", (sc & 0xf0) >> 4, sc & 0xf); - *result = AEE_EBADPARM; - return 1; + return decline_request(sc, AEE_EBADPARM, result, returned); } if (handle >= n_ifaces) { fprintf(stderr, "Unsupported handle: %u\n", handle); - *result = AEE_EUNSUPPORTED; - return 1; + return decline_request(sc, AEE_EUNSUPPORTED, result, returned); } if (method >= ifaces[handle]->n_procs) { fprintf(stderr, "Unsupported method: %u (%08x)\n", method, sc); - *result = AEE_EUNSUPPORTED; - return 1; + return decline_request(sc, AEE_EUNSUPPORTED, result, returned); } impl = &ifaces[handle]->procs[method]; if (impl->def == NULL || impl->impl == NULL) { fprintf(stderr, "Unsupported method: %u (%08x)\n", method, sc); - *result = AEE_EUNSUPPORTED; - return 1; + return decline_request(sc, AEE_EUNSUPPORTED, result, returned); } ret = count_sizes4(impl->def, REMOTE_SCALARS_INBUFS(sc), REMOTE_SCALARS_OUTBUFS(sc), decoded); if (ret) { fprintf(stderr, "Method invoked with less arguments than expected\n"); - *result = AEE_EBADPARM; - return 1; + return decline_request(sc, AEE_EBADPARM, result, returned); } *returned = alloc_outbufs4(impl->def, decoded, REMOTE_SCALARS_OUTBUFS(sc)); if (*returned == NULL && impl->def->n_args > 0) { perror("Could not allocate output buffers"); - *result = AEE_ENOMEMORY; - return 1; + return decline_request(sc, AEE_ENOMEMORY, result, returned); } *result = impl->impl(ifaces[handle]->data, decoded, *returned); From 7f2fc1d5afdd811416a4b1bb5664f2a932f7855f Mon Sep 17 00:00:00 2001 From: Fady Date: Sat, 19 Sep 2026 23:58:47 +0200 Subject: [PATCH 4/5] rpcd_builder: map the registry's parent directory when DIR/sensors/persist exists The framework writes /persist/sensors/registry/fstempfile and renames it into /persist/sensors/registry/registry, and it looks for sns_reg_version and parsed_file_list.csv next to the registry directory (it creates sns_reg_version through that temporary file when the file is missing). With DIR/sensors/persist/ present that directory backs the whole of /persist/sensors/registry, its registry/ subdirectory being the registry; without it the tree is as before. Link: https://github.com/linux-msm/hexagonrpc/issues/19 Link: https://github.com/linux-msm/hexagonrpc/pull/21 Signed-off-by: Fady --- hexagonrpcd/rpcd_builder.c | 50 ++++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/hexagonrpcd/rpcd_builder.c b/hexagonrpcd/rpcd_builder.c index d374958..0d720b3 100644 --- a/hexagonrpcd/rpcd_builder.c +++ b/hexagonrpcd/rpcd_builder.c @@ -109,6 +109,48 @@ static struct hexagonfs_dirent *hfs_map_or_empty(const char *name, const char *p * * TODO: Make this free()-able with reference counts */ +#define SENSORS_PERSIST "/sensors/persist/" + +/* + * The sensor framework does not only read its registry: while it initialises + * it removes and rewrites entries below /persist/sensors/registry/registry + * and writes a temporary file next to that directory + * (/persist/sensors/registry/fstempfile, renamed into the registry). With + * PREFIX/sensors/persist/ present, that directory backs the whole of + * /persist/sensors/registry, its registry/ subdirectory being the registry; + * otherwise the parent stays virtual and PREFIX/sensors/registry/ is the + * registry, as before. + */ +static struct hexagonfs_dirent *build_persist_dir(const char *prefix, + const char *sns_reg) +{ + struct stat st; + char *sns_persist; + + sns_persist = malloc(strlen(prefix) + strlen(SENSORS_PERSIST) + 1); + if (sns_persist != NULL) { + strcpy(sns_persist, prefix); + strcat(sns_persist, SENSORS_PERSIST); + + if (stat(sns_persist, &st) == 0 && S_ISDIR(st.st_mode)) + return hfs_mkdir("persist", 1, + hfs_mkdir("sensors", 1, + hfs_map("registry", sns_persist) + ) + ); + + free(sns_persist); + } + + return hfs_mkdir("persist", 1, + hfs_mkdir("sensors", 1, + hfs_mkdir("registry", 1, + hfs_map("registry", sns_reg) + ) + ) + ); +} + struct hexagonfs_dirent *construct_root_dir(const char *prefix, const char *dsp) { char *acdbdata, *dsp_libs, *sns_cfg, *sns_reg, *sns_reg_config, *socinfo; @@ -160,13 +202,7 @@ struct hexagonfs_dirent *construct_root_dir(const char *prefix, const char *dsp) * Some platforms need this in / and some need it in /mnt/vendor. Form * a hard link between both locations. */ - persist_dir = hfs_mkdir("persist", 1, - hfs_mkdir("sensors", 1, - hfs_mkdir("registry", 1, - hfs_map("registry", sns_reg) - ) - ) - ); + persist_dir = build_persist_dir(prefix, sns_reg); /* * Some platforms need vendor in / and some need it in /system. Form From 40e50412812adce9dddc2be9a4510ebce05eef89 Mon Sep 17 00:00:00 2001 From: Fady Date: Sun, 20 Sep 2026 16:09:14 +0200 Subject: [PATCH 5/5] hexagonrpcd: fix descriptor lifetimes and the write paths added for the registry hexagonfs_openat hands out a second file number when a name's directory part resolves to an already-assigned descriptor (".", "..", "/"), so the close in create/unlink/rename freed one still held elsewhere; hexagonfs_close now destroys only on the last reference, and fastrpc_apps_std_deinit closes descendants before the root. mapped_or_empty pointed write and truncate at implementations that dereference a NULL context for an absent directory. outbufs_calculate_size padded zero-length buffers outbufs_encode never writes, and alloc_outbufs4 did not skip the method id extended methods carry in the first primitive word. A '+' mode no longer implies O_CREAT and falls back to a read-only open on an unwritable tree, stat reports the write bit only when the backing file has it, mapped writes loop until done, and the persist layout is probed at DIR/sensors/persist/registry rather than its parent. Signed-off-by: Fady --- hexagonrpcd/apps_std.c | 37 +++++++++++++++------ hexagonrpcd/hexagonfs.c | 20 ++++++++---- hexagonrpcd/hexagonfs.h | 3 +- hexagonrpcd/hexagonfs_mapped.c | 60 +++++++++++++++++++++++++++++----- hexagonrpcd/iobuffer.c | 12 +++++-- hexagonrpcd/listener.c | 19 +++++++---- hexagonrpcd/rpcd_builder.c | 39 ++++++++++++++-------- 7 files changed, 139 insertions(+), 51 deletions(-) diff --git a/hexagonrpcd/apps_std.c b/hexagonrpcd/apps_std.c index c1a8cb8..f5aeaba 100644 --- a/hexagonrpcd/apps_std.c +++ b/hexagonrpcd/apps_std.c @@ -153,11 +153,9 @@ static uint32_t apps_std_fseek(void *data, } /* - * Open NAME below DIRFD the way fopen(3) would for MODE: read-only for "r", - * otherwise (w, a, +) read-write, created if absent, truncated for "w", - * appended for "a". The sensor framework of the Snapdragon Sensor Core - * writes into its registry directory while it initialises and aborts the - * whole DSP when that is refused. + * Open NAME below DIRFD as fopen(3) would for MODE. The Snapdragon Sensor + * Core writes into its registry while it initialises and takes the whole DSP + * down when that is refused. */ static int apps_std_open_mode(struct apps_std_ctx *ctx, int dirfd, const char *name, const char *mode) @@ -165,16 +163,17 @@ static int apps_std_open_mode(struct apps_std_ctx *ctx, int dirfd, const char *m; bool writing = false; int flags = 0; + int ret; for (m = mode; *m != '\0'; m++) { switch (*m) { case 'w': writing = true; - flags |= O_TRUNC; + flags |= O_CREAT | O_TRUNC; break; case 'a': writing = true; - flags |= O_APPEND; + flags |= O_CREAT | O_APPEND; break; case '+': writing = true; @@ -187,7 +186,17 @@ static int apps_std_open_mode(struct apps_std_ctx *ctx, int dirfd, if (!writing) return hexagonfs_openat(ctx->fds, ctx->rootfd, dirfd, name); - return hexagonfs_create(ctx->fds, ctx->rootfd, dirfd, name, flags); + ret = hexagonfs_create(ctx->fds, ctx->rootfd, dirfd, name, flags); + + /* + * "r+" must not create the file, and on a read-only tree the DSP is + * normally only going to read it. + */ + if (ret < 0 && !(flags & O_CREAT) + && (ret == -EACCES || ret == -EROFS || ret == -ENOSYS)) + ret = hexagonfs_openat(ctx->fds, ctx->rootfd, dirfd, name); + + return ret; } static uint32_t apps_std_fopen(void *data, @@ -593,11 +602,19 @@ void fastrpc_apps_std_deinit(struct fastrpc_interface *iface) struct apps_std_ctx *ctx = iface->data; int i; - for (i = 0; i < HEXAGONFS_MAX_FD; i++) { - if (ctx->fds[i] != NULL) + /* + * Destroying a descriptor walks its ->up chain, so close descendants + * before the root rather than ascending from it. + */ + for (i = HEXAGONFS_MAX_FD - 1; i >= 0; i--) { + if (i != ctx->rootfd && ctx->fds[i] != NULL) hexagonfs_close(ctx->fds, i); } + if (ctx->rootfd >= 0 && ctx->rootfd < HEXAGONFS_MAX_FD + && ctx->fds[ctx->rootfd] != NULL) + hexagonfs_close(ctx->fds, ctx->rootfd); + free(iface->data); free(iface); } diff --git a/hexagonrpcd/hexagonfs.c b/hexagonrpcd/hexagonfs.c index da0c95f..603a40a 100644 --- a/hexagonrpcd/hexagonfs.c +++ b/hexagonrpcd/hexagonfs.c @@ -194,6 +194,7 @@ int hexagonfs_openat(struct hexagonfs_fd **fds, int rootfd, int dirfd, const cha int hexagonfs_close(struct hexagonfs_fd **fds, int fileno) { struct hexagonfs_fd *fd; + size_t i; if (fileno < 0 || fileno >= HEXAGONFS_MAX_FD) return -EBADF; @@ -202,11 +203,20 @@ int hexagonfs_close(struct hexagonfs_fd **fds, int fileno) if (fd == NULL || fd->ops == NULL) return -EBADF; + fds[fileno] = NULL; + + /* + * ".", ".." and the search directory itself can be handed out under a + * second file number: only the last reference may destroy it. + */ + for (i = 0; i < HEXAGONFS_MAX_FD; i++) { + if (fds[i] == fd) + return 0; + } + fd->is_assigned = false; destroy_file_descriptor(fd); - fds[fileno] = NULL; - return 0; } @@ -279,10 +289,8 @@ int hexagonfs_fstat(struct hexagonfs_fd **fds, int fileno, struct stat *stats) } /* - * Resolve the directory part of NAME for the operations that act on a name inside a directory (create, - * unlink, rename). Returns the file number of that directory and sets *BASE to the final component. When - * NAME has no directory part, DIRFD itself is returned and *OWNED stays false; otherwise the directory was - * opened here and the caller closes it. + * Resolve the directory part of NAME, setting *BASE to the final component. + * *OWNED is true when the directory was opened here and the caller closes it. */ static int resolve_parent(struct hexagonfs_fd **fds, int rootfd, int dirfd, const char *name, const char **base, bool *owned) diff --git a/hexagonrpcd/hexagonfs.h b/hexagonrpcd/hexagonfs.h index b655f3d..e24b4b5 100644 --- a/hexagonrpcd/hexagonfs.h +++ b/hexagonrpcd/hexagonfs.h @@ -42,8 +42,7 @@ struct hexagonfs_file_ops { ssize_t (*read)(struct hexagonfs_fd *fd, size_t size, void *ptr); int (*stat)(struct hexagonfs_fd *fd, struct stat *stats); int (*seek)(struct hexagonfs_fd *fd, off_t off, int whence); - /* Writable backends only (physical directories): create/open NAME for reading and writing below DIR - * (FLAGS: O_TRUNC, O_APPEND), write, truncate, unlink, rename within the same backend. */ + /* Writable backends only; NULL here is reported as ENOSYS. */ int (*create)(struct hexagonfs_fd *dir, const char *name, int flags, diff --git a/hexagonrpcd/hexagonfs_mapped.c b/hexagonrpcd/hexagonfs_mapped.c index 9804527..400800f 100644 --- a/hexagonrpcd/hexagonfs_mapped.c +++ b/hexagonrpcd/hexagonfs_mapped.c @@ -162,7 +162,8 @@ static int mapped_create(struct hexagonfs_fd *dir, } ctx->fd = openat(dir_ctx->fd, name, - O_RDWR | O_CREAT | O_CLOEXEC | (flags & (O_TRUNC | O_APPEND)), + O_RDWR | O_CLOEXEC + | (flags & (O_CREAT | O_TRUNC | O_APPEND)), 0644); if (ctx->fd == -1) { ret = -errno; @@ -190,13 +191,30 @@ static int mapped_create(struct hexagonfs_fd *dir, static ssize_t mapped_write(struct hexagonfs_fd *fd, size_t size, const void *in) { struct mapped_ctx *ctx = fd->data; + const char *buf = in; + size_t done = 0; ssize_t ret; - ret = write(ctx->fd, in, size); - if (ret < 0) - return -errno; + /* + * A short write reported as success lets the DSP rename a truncated + * file into place. + */ + while (done < size) { + ret = write(ctx->fd, &buf[done], size - done); + if (ret < 0) { + if (errno == EINTR) + continue; - return ret; + return done ? (ssize_t) done : -errno; + } + + if (ret == 0) + break; + + done += ret; + } + + return done; } static int mapped_truncate(struct hexagonfs_fd *fd, off_t len) @@ -310,13 +328,20 @@ static int mapped_stat(struct hexagonfs_fd *fd, struct stat *stats) if (phys.st_mode & S_IFDIR) { stats->st_mode = S_IFDIR - | S_IRUSR | S_IWUSR | S_IXUSR + | S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; } else { - stats->st_mode = S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + stats->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; } + /* + * Claiming a write bit the backing file lacks makes the DSP take a + * write path that can only fail. + */ + if (phys.st_mode & S_IWUSR) + stats->st_mode |= S_IWUSR; + stats->st_atim.tv_sec = phys.st_atim.tv_sec; stats->st_atim.tv_nsec = phys.st_atim.tv_nsec; stats->st_ctim.tv_sec = phys.st_ctim.tv_sec; @@ -403,6 +428,23 @@ static int mapped_or_empty_rename(struct hexagonfs_fd *dir, const char *name, return -ENOENT; } +static ssize_t mapped_or_empty_write(struct hexagonfs_fd *fd, size_t size, + const void *in) +{ + if (fd->data) + return mapped_write(fd, size, in); + else + return -ENOENT; +} + +static int mapped_or_empty_truncate(struct hexagonfs_fd *fd, off_t len) +{ + if (fd->data) + return mapped_truncate(fd, len); + else + return -ENOENT; +} + static int mapped_or_empty_seek(struct hexagonfs_fd *fd, off_t off, int whence) { if (fd->data) @@ -485,8 +527,8 @@ struct hexagonfs_file_ops hexagonfs_mapped_or_empty_ops = { .seek = mapped_or_empty_seek, .stat = mapped_or_empty_stat, .create = mapped_or_empty_create, - .write = mapped_write, - .truncate = mapped_truncate, + .write = mapped_or_empty_write, + .truncate = mapped_or_empty_truncate, .unlink = mapped_or_empty_unlink, .rename = mapped_or_empty_rename, }; diff --git a/hexagonrpcd/iobuffer.c b/hexagonrpcd/iobuffer.c index 0c36df6..b4769b6 100644 --- a/hexagonrpcd/iobuffer.c +++ b/hexagonrpcd/iobuffer.c @@ -199,9 +199,15 @@ size_t outbufs_calculate_size(size_t n_outbufs, const struct fastrpc_io_buffer * for (i = 0; i < n_outbufs; i++) { size += 4; - if (size & 0x7) - size += 8 - (size & 0x7); - size += outbufs[i].s; + /* + * outbufs_encode pads only when there is a payload; padding + * an empty buffer would ship uninitialised bytes. + */ + if (outbufs[i].s) { + if (size & 0x7) + size += 8 - (size & 0x7); + size += outbufs[i].s; + } } return size; diff --git a/hexagonrpcd/listener.c b/hexagonrpcd/listener.c index 8912849..5b3ba6e 100644 --- a/hexagonrpcd/listener.c +++ b/hexagonrpcd/listener.c @@ -299,6 +299,13 @@ static struct fastrpc_io_buffer *alloc_outbufs4(const struct hrpc_method_def_int if (out == NULL) return NULL; + /* + * Extended methods carry their id in the first primitive word; step + * over it as count_sizes4 does. + */ + if (def->msg_id > 30) + prim_in = (const uint32_t *) prim_in + 1; + for (i = 0; i < def->n_args; i++) { if (def->args[i].t == HRPC_ARG_OUT_BLOB || def->args[i].t == HRPC_ARG_OUT_TYPE) { @@ -419,10 +426,9 @@ static struct fastrpc_io_buffer *alloc_outbufs4(const struct hrpc_method_def_int } /* - * adsp_listener_next2 inlines at most this many bytes of a request's input - * buffers and reports the full length. A longer request (the sensor - * framework writing a file of tens of kilobytes) is completed with - * adsp_listener_get_in_bufs2 from that offset, as Qualcomm's listener does. + * adsp_listener_next2 inlines at most this many bytes and reports the full + * length; the rest is fetched with adsp_listener_get_in_bufs2, as Qualcomm's + * listener does. */ #define LISTENER_INLINE_INBUFS 256 #define LISTENER_MAX_INBUFS (16 * 1024 * 1024) @@ -550,9 +556,8 @@ static int return_for_next_invoke(int fd, /* * A request this server cannot serve is answered with an error and empty * output buffers, so that the DSP sees a failed call rather than a vanished - * listener; the DSP-side sensor framework retries or continues on an error - * but aborts without the answer. Returns 1 only when even that answer cannot - * be built. + * listener: the sensor framework retries or continues on an error but aborts + * without the answer. */ static int decline_request(uint32_t sc, uint32_t error, uint32_t *result, struct fastrpc_io_buffer **returned) diff --git a/hexagonrpcd/rpcd_builder.c b/hexagonrpcd/rpcd_builder.c index 0d720b3..58a4fad 100644 --- a/hexagonrpcd/rpcd_builder.c +++ b/hexagonrpcd/rpcd_builder.c @@ -110,38 +110,49 @@ static struct hexagonfs_dirent *hfs_map_or_empty(const char *name, const char *p * TODO: Make this free()-able with reference counts */ #define SENSORS_PERSIST "/sensors/persist/" +#define SENSORS_PERSIST_REG "/sensors/persist/registry" /* - * The sensor framework does not only read its registry: while it initialises - * it removes and rewrites entries below /persist/sensors/registry/registry - * and writes a temporary file next to that directory - * (/persist/sensors/registry/fstempfile, renamed into the registry). With - * PREFIX/sensors/persist/ present, that directory backs the whole of - * /persist/sensors/registry, its registry/ subdirectory being the registry; - * otherwise the parent stays virtual and PREFIX/sensors/registry/ is the - * registry, as before. + * The framework rewrites entries below /persist/sensors/registry/registry and + * renames a temporary file into it, so that tree has to be physical. When + * DIR/sensors/persist/registry exists it backs the whole of + * /persist/sensors/registry; otherwise the layout is as before. */ static struct hexagonfs_dirent *build_persist_dir(const char *prefix, const char *sns_reg) { struct stat st; - char *sns_persist; + char *sns_persist, *probe; + size_t n_prefix = strlen(prefix); - sns_persist = malloc(strlen(prefix) + strlen(SENSORS_PERSIST) + 1); - if (sns_persist != NULL) { + sns_persist = malloc(n_prefix + strlen(SENSORS_PERSIST) + 1); + probe = malloc(n_prefix + strlen(SENSORS_PERSIST_REG) + 1); + + if (sns_persist != NULL && probe != NULL) { strcpy(sns_persist, prefix); strcat(sns_persist, SENSORS_PERSIST); + strcpy(probe, prefix); + strcat(probe, SENSORS_PERSIST_REG); + + /* + * The registry itself has to be there, not just its parent: + * mapping a persist directory that does not contain one hides + * the packaged registry, and nothing can create it at runtime. + */ + if (stat(probe, &st) == 0 && S_ISDIR(st.st_mode)) { + free(probe); - if (stat(sns_persist, &st) == 0 && S_ISDIR(st.st_mode)) return hfs_mkdir("persist", 1, hfs_mkdir("sensors", 1, hfs_map("registry", sns_persist) ) ); - - free(sns_persist); + } } + free(probe); + free(sns_persist); + return hfs_mkdir("persist", 1, hfs_mkdir("sensors", 1, hfs_mkdir("registry", 1,