Introduce the core 9P network protocol client implementation. 9P is a network filesystem protocol originally developed for the Plan 9 operating system. Add the baseline 9P2000.L protocol handling, including message serialization, client registration, and basic transport operations.
Signed-off-by: Kuan-Wei Chiu <[email protected]> --- include/9p.h | 82 +++++++++++++++ net/9p/Kconfig | 5 + net/9p/Makefile | 5 + net/9p/client.c | 272 ++++++++++++++++++++++++++++++++++++++++++++++++ net/Kconfig | 2 + net/Makefile | 1 + 6 files changed, 367 insertions(+) create mode 100644 include/9p.h create mode 100644 net/9p/Kconfig create mode 100644 net/9p/Makefile create mode 100644 net/9p/client.c diff --git a/include/9p.h b/include/9p.h new file mode 100644 index 00000000000..5e46daa3414 --- /dev/null +++ b/include/9p.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2026, Kuan-Wei Chiu <[email protected]> + */ + +#ifndef __9P_H__ +#define __9P_H__ + +#include <linux/types.h> + +#define P9_MSIZE 65536 +#define P9_NOFID (~0U) +#define P9_NOTAG 0xFFFF +#define P9_REQ_TAG 1 + +#define P9_RLERROR 7 +#define P9_TLOPEN 12 +#define P9_RLOPEN 13 +#define P9_TGETATTR 24 +#define P9_RGETATTR 25 +#define P9_TREADDIR 40 +#define P9_RREADDIR 41 +#define P9_TVERSION 100 +#define P9_RVERSION 101 +#define P9_TATTACH 104 +#define P9_RATTACH 105 +#define P9_TWALK 110 +#define P9_RWALK 111 +#define P9_TREAD 116 +#define P9_RREAD 117 +#define P9_TCLUNK 120 +#define P9_RCLUNK 121 + +#define P9_O_RDONLY 00000000 +#define P9_GETATTR_SIZE 0x00000010ULL +#define P9_GETATTR_SIZE_OFFSET 41 +#define P9_DIRENT_OFFSET 21 +#define P9_DT_DIR 4 + +struct p9_header { + u32 size; + u8 id; + u16 tag; +} __packed; + +struct p9_client; + +struct p9_trans_ops { + int (*request)(struct p9_client *c, void *tx, int tx_len, void *rx, int rx_len); +}; + +struct p9_client { + const struct p9_trans_ops *ops; + void *priv; + u8 *tx; + u8 *rx; + u32 next_fid; +}; + +void p9_register_client(struct p9_client *c); +struct p9_client *p9_get_client(void); + +u32 p9_client_alloc_fid(struct p9_client *c); +int p9_client_version(struct p9_client *c); +int p9_client_attach(struct p9_client *c, u32 fid, const char *uname, const char *aname); +int p9_client_walk(struct p9_client *c, u32 base_fid, u32 new_fid, const char *path); +int p9_client_lopen(struct p9_client *c, u32 fid, u32 flags); +int p9_client_clunk(struct p9_client *c, u32 fid); +int p9_client_stat(struct p9_client *c, u32 fid, loff_t *size); +int p9_client_read(struct p9_client *c, u32 fid, u64 offset, u32 count, void *buf, u32 *actread); +int p9_client_readdir(struct p9_client *c, u32 fid, u64 offset, u32 count, void *buf, u32 *actread); + +struct blk_desc; +struct disk_partition; + +int p9_fs_probe(struct blk_desc *fs_dev_desc, struct disk_partition *fs_partition); +int p9_fs_ls(const char *dirname); +int p9_fs_exists(const char *filename); +int p9_fs_size(const char *filename, loff_t *size); +int p9_fs_read(const char *filename, void *buf, loff_t offset, loff_t len, loff_t *actread); + +#endif /* __9P_H__ */ diff --git a/net/9p/Kconfig b/net/9p/Kconfig new file mode 100644 index 00000000000..3681e0b6d6d --- /dev/null +++ b/net/9p/Kconfig @@ -0,0 +1,5 @@ +config NET_9P + bool "9P protocol support" + help + This enables support for the 9P network protocol (9P2000). + It implements the core 9P client and protocol handling. diff --git a/net/9p/Makefile b/net/9p/Makefile new file mode 100644 index 00000000000..e9220b0826e --- /dev/null +++ b/net/9p/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2026, Kuan-Wei Chiu <[email protected]> + +obj-y += client.o diff --git a/net/9p/client.c b/net/9p/client.c new file mode 100644 index 00000000000..038cb7a78c0 --- /dev/null +++ b/net/9p/client.c @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026, Kuan-Wei Chiu <[email protected]> + */ + +#include <9p.h> +#include <config.h> +#include <log.h> +#include <malloc.h> +#include <asm/cache.h> +#include <asm/unaligned.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/types.h> + +static struct p9_client *current_client; + +void p9_register_client(struct p9_client *c) +{ + current_client = c; +} + +struct p9_client *p9_get_client(void) +{ + return current_client; +} + +u32 p9_client_alloc_fid(struct p9_client *c) +{ + if (c->next_fid >= 0xFFFF0000 || c->next_fid == 0) + c->next_fid = 2; /* fid 1 is reserved for root */ + return c->next_fid++; +} + +static void p9_put_u16(u8 **p, u16 v) +{ + put_unaligned_le16(v, *p); + *p += 2; +} + +static void p9_put_u32(u8 **p, u32 v) +{ + put_unaligned_le32(v, *p); + *p += 4; +} + +static void p9_put_u64(u8 **p, u64 v) +{ + put_unaligned_le64(v, *p); *p += 8; +} + +static void p9_put_str(u8 **p, const char *s) +{ + u16 len = strlen(s); + + put_unaligned_le16(len, *p); + *p += 2; + memcpy(*p, s, len); + *p += len; +} + +static int p9_check_error(struct p9_client *c, int expected_id, const char *step) +{ + struct p9_header *rx_hdr = (struct p9_header *)c->rx; + u32 ecode; + + if (rx_hdr->id == P9_RLERROR) { + ecode = get_unaligned_le32(c->rx + 7); + log_err("9P Error: %s failed (ecode=%u)\n", step, ecode); + return -EIO; + } else if (rx_hdr->id != expected_id) { + log_err("9P Error: %s got id %d (expected %d)\n", step, rx_hdr->id, expected_id); + return -EIO; + } + return 0; +} + +int p9_client_version(struct p9_client *c) +{ + struct p9_header *hdr; + u8 *ptr; + + if (!c->tx) + c->tx = memalign(ARCH_DMA_MINALIGN, P9_MSIZE); + if (!c->rx) + c->rx = memalign(ARCH_DMA_MINALIGN, P9_MSIZE); + + hdr = (struct p9_header *)c->tx; + ptr = c->tx + sizeof(*hdr); + p9_put_u32(&ptr, P9_MSIZE); + p9_put_str(&ptr, "9P2000.L"); + + hdr->size = ptr - c->tx; + hdr->id = P9_TVERSION; + hdr->tag = P9_NOTAG; + + if (c->ops->request(c, c->tx, hdr->size, c->rx, P9_MSIZE) < 0) + return -EIO; + + return p9_check_error(c, P9_RVERSION, "Tversion"); +} + +int p9_client_attach(struct p9_client *c, u32 fid, const char *uname, const char *aname) +{ + struct p9_header *hdr = (struct p9_header *)c->tx; + u8 *ptr = c->tx + sizeof(*hdr); + + p9_put_u32(&ptr, fid); + p9_put_u32(&ptr, P9_NOFID); + p9_put_str(&ptr, uname); + p9_put_str(&ptr, aname); + p9_put_u32(&ptr, 0); + + hdr->size = ptr - c->tx; + hdr->id = P9_TATTACH; + hdr->tag = P9_REQ_TAG; + + if (c->ops->request(c, c->tx, hdr->size, c->rx, P9_MSIZE) < 0) + return -EIO; + + return p9_check_error(c, P9_RATTACH, "Tattach"); +} + +int p9_client_walk(struct p9_client *c, u32 base_fid, u32 new_fid, const char *path) +{ + struct p9_header *hdr = (struct p9_header *)c->tx; + u8 *ptr = c->tx + sizeof(*hdr); + char path_buf[128]; + char *wnames[16]; + char *token, *p = path_buf; + int nwname = 0, i; + + strlcpy(path_buf, path, sizeof(path_buf)); + while ((token = strsep(&p, "/")) != NULL) { + if (*token == '\0' || strcmp(token, ".") == 0) + continue; + if (nwname >= 16) { + log_err("9P: Path too deep (max 16 levels)\n"); + return -EINVAL; + } + wnames[nwname++] = token; + } + + p9_put_u32(&ptr, base_fid); + p9_put_u32(&ptr, new_fid); + p9_put_u16(&ptr, nwname); + for (i = 0; i < nwname; i++) + p9_put_str(&ptr, wnames[i]); + + hdr->size = ptr - c->tx; + hdr->id = P9_TWALK; + hdr->tag = P9_REQ_TAG; + + if (c->ops->request(c, c->tx, hdr->size, c->rx, P9_MSIZE) < 0) + return -EIO; + + return p9_check_error(c, P9_RWALK, "Twalk"); +} + +int p9_client_lopen(struct p9_client *c, u32 fid, u32 flags) +{ + struct p9_header *hdr = (struct p9_header *)c->tx; + u8 *ptr = c->tx + sizeof(*hdr); + + p9_put_u32(&ptr, fid); + p9_put_u32(&ptr, flags); + + hdr->size = ptr - c->tx; + hdr->id = P9_TLOPEN; + hdr->tag = P9_REQ_TAG; + + if (c->ops->request(c, c->tx, hdr->size, c->rx, P9_MSIZE) < 0) + return -EIO; + + return p9_check_error(c, P9_RLOPEN, "Tlopen"); +} + +int p9_client_clunk(struct p9_client *c, u32 fid) +{ + struct p9_header *hdr = (struct p9_header *)c->tx; + u8 *ptr = c->tx + sizeof(*hdr); + + p9_put_u32(&ptr, fid); + hdr->size = ptr - c->tx; + hdr->id = P9_TCLUNK; + hdr->tag = P9_REQ_TAG; + + return c->ops->request(c, c->tx, hdr->size, c->rx, P9_MSIZE); +} + +int p9_client_stat(struct p9_client *c, u32 fid, loff_t *size) +{ + struct p9_header *hdr = (struct p9_header *)c->tx; + u8 *ptr = c->tx + sizeof(*hdr); + u64 valid; + + p9_put_u32(&ptr, fid); + p9_put_u64(&ptr, P9_GETATTR_SIZE); + + hdr->size = ptr - c->tx; + hdr->id = P9_TGETATTR; + hdr->tag = P9_REQ_TAG; + + if (c->ops->request(c, c->tx, hdr->size, c->rx, P9_MSIZE) < 0) + return -EIO; + + if (p9_check_error(c, P9_RGETATTR, "Tgetattr") == 0) { + ptr = c->rx + sizeof(*hdr); + valid = get_unaligned_le64(ptr); + ptr += sizeof(valid); + ptr += P9_GETATTR_SIZE_OFFSET; + if (valid & P9_GETATTR_SIZE) { + *size = get_unaligned_le64(ptr); + return 0; + } + } + return -EIO; +} + +int p9_client_read(struct p9_client *c, u32 fid, u64 offset, u32 count, void *buf, u32 *actread) +{ + struct p9_header *hdr = (struct p9_header *)c->tx; + u8 *ptr = c->tx + sizeof(*hdr); + u32 max_read = P9_MSIZE - 24; + u32 read_len = (count == 0 || count > max_read) ? max_read : count; + + p9_put_u32(&ptr, fid); + p9_put_u64(&ptr, offset); + p9_put_u32(&ptr, read_len); + + hdr->size = ptr - c->tx; + hdr->id = P9_TREAD; + hdr->tag = P9_REQ_TAG; + + if (c->ops->request(c, c->tx, hdr->size, c->rx, P9_MSIZE) < 0) + return -EIO; + + if (p9_check_error(c, P9_RREAD, "Tread") == 0) { + ptr = c->rx + sizeof(*hdr); + *actread = get_unaligned_le32(ptr); + ptr += 4; + memcpy(buf, ptr, *actread); + return 0; + } + return -EIO; +} + +int p9_client_readdir(struct p9_client *c, u32 fid, u64 offset, u32 count, void *buf, u32 *actread) +{ + struct p9_header *hdr = (struct p9_header *)c->tx; + u8 *ptr = c->tx + sizeof(*hdr); + + p9_put_u32(&ptr, fid); + p9_put_u64(&ptr, offset); + p9_put_u32(&ptr, count); + + hdr->size = ptr - c->tx; + hdr->id = P9_TREADDIR; + hdr->tag = P9_REQ_TAG; + + if (c->ops->request(c, c->tx, hdr->size, c->rx, P9_MSIZE) < 0) + return -EIO; + + if (p9_check_error(c, P9_RREADDIR, "Treaddir") == 0) { + ptr = c->rx + sizeof(*hdr); + *actread = get_unaligned_le32(ptr); + ptr += 4; + memcpy(buf, ptr, *actread); + return 0; + } + return -EIO; +} diff --git a/net/Kconfig b/net/Kconfig index 386376ce884..00448ab4cc6 100644 --- a/net/Kconfig +++ b/net/Kconfig @@ -236,6 +236,8 @@ endif # if NET_LEGACY source "net/lwip/Kconfig" +source "net/9p/Kconfig" + config BOOTDEV_ETH bool "Enable bootdev for ethernet" depends on BOOTSTD diff --git a/net/Makefile b/net/Makefile index ceac6de6377..7745607248b 100644 --- a/net/Makefile +++ b/net/Makefile @@ -49,3 +49,4 @@ obj-y += net-common.o endif obj-$(CONFIG_NET_LWIP) += lwip/ +obj-$(CONFIG_NET_9P) += 9p/ -- 2.55.0.rc2.803.g1fd1e6609c-goog

