diff --git a/app/tests/include/app/tests.h b/app/tests/include/app/tests.h index b93f127876..ecfe3b1b65 100644 --- a/app/tests/include/app/tests.h +++ b/app/tests/include/app/tests.h @@ -19,6 +19,7 @@ int benchmarks(int argc, const console_cmd_args *argv); int clock_tests(int argc, const console_cmd_args *argv); int printf_tests(int argc, const console_cmd_args *argv); int printf_tests_float(int argc, const console_cmd_args *argv); +int v9p_tests(int argc, const console_cmd_args *argv); #endif diff --git a/app/tests/rules.mk b/app/tests/rules.mk index 571d5e7f89..cb8ffd9767 100644 --- a/app/tests/rules.mk +++ b/app/tests/rules.mk @@ -11,6 +11,7 @@ MODULE_SRCS := \ $(LOCAL_DIR)/tests.c \ $(LOCAL_DIR)/thread_tests.c \ $(LOCAL_DIR)/port_tests.c \ + $(LOCAL_DIR)/v9p_tests.c \ MODULE_FLOAT_SRCS := \ $(LOCAL_DIR)/benchmarks.c \ diff --git a/app/tests/tests.c b/app/tests/tests.c index c31f958390..900330bd17 100644 --- a/app/tests/tests.c +++ b/app/tests/tests.c @@ -22,4 +22,5 @@ STATIC_COMMAND("bench", "miscellaneous benchmarks", &benchmarks) STATIC_COMMAND("fibo", "threaded fibonacci", &fibo) STATIC_COMMAND("spinner", "create a spinning thread", &spinner) STATIC_COMMAND("cbuf_tests", "test lib/cbuf", &cbuf_tests) +STATIC_COMMAND("v9p_tests", "test dev/virtio/9p", &v9p_tests) STATIC_COMMAND_END(tests); diff --git a/app/tests/v9p_tests.c b/app/tests/v9p_tests.c new file mode 100644 index 0000000000..acb8986f5f --- /dev/null +++ b/app/tests/v9p_tests.c @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2023 Cody Wong + * + * Use of this source code is governed by a MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT + */ +#include +#include +#include + +#define FID_ROOT 1 +#define FID_RO 2 + +#define _LOGF(fmt, args...) \ + printf("[%s:%d] " fmt, __PRETTY_FUNCTION__, __LINE__, ##args) +#define LOGF(x...) _LOGF(x) + +#if WITH_DEV_VIRTIO_9P +#include +#include + +int v9p_tests(int argc, const console_cmd_args *argv) { + struct virtio_device *dev = virtio_get_9p_device(0); + status_t status; + + if (dev == NULL) { + LOGF("v9p device doesn't exist\n"); + return ERR_NOT_FOUND; + } + + virtio_9p_msg_t tatt = { + .msg_type = P9_TATTACH, + .tag = P9_TAG_DEFAULT, + .msg.tattach = { + .fid = FID_ROOT, + .afid = P9_FID_NOFID, + .uname = "root", + .aname = V9P_MOUNT_ANAME, + .n_uname = P9_UNAME_NONUNAME + } + }; + virtio_9p_msg_t ratt = {}; + + status = virtio_9p_rpc(dev, &tatt, &ratt); + if (status != NO_ERROR) { + LOGF("failed to attach to the host shared folder: %d\n", status); + return status; + } + + virtio_9p_msg_t twalk = { + .msg_type = P9_TWALK, + .tag = P9_TAG_DEFAULT, + .msg.twalk = { + .fid = FID_ROOT, .newfid = FID_RO, .nwname = 1, + .wname = {"LICENSE"} + } + }; + virtio_9p_msg_t rwalk = {}; + + status = virtio_9p_rpc(dev, &twalk, &rwalk); + if (status != NO_ERROR) { + LOGF("failed to walk to the target file: %d\n", status); + return status; + } + + virtio_9p_msg_t tlopen = { + .msg_type= P9_TLOPEN, + .tag = P9_TAG_DEFAULT, + .msg.tlopen = { + .fid = FID_RO, .flags = O_RDWR, + } + }; + virtio_9p_msg_t rlopen = {}; + + status = virtio_9p_rpc(dev, &tlopen, &rlopen); + if (status != NO_ERROR) { + LOGF("failed to open the target file: %d\n", status); + return status; + } + + virtio_9p_msg_t tread = { + .msg_type= P9_TREAD, + .tag = P9_TAG_DEFAULT, + .msg.tread = { + .fid = FID_RO, .offset = 0, .count = 1024 + } + }; + virtio_9p_msg_t rread = {}; + + status = virtio_9p_rpc(dev, &tread, &rread); + if (status != NO_ERROR) { + LOGF("failed to read the target file: %d\n", status); + return status; + } + + hexdump8(rread.msg.rread.data, rread.msg.rread.count); + + return NO_ERROR; +} +#else +int v9p_tests(int argc, const console_cmd_args *argv) { + LOGF("platform didn't have dev/virtio/9p supported\n"); + return ERR_NOT_SUPPORTED; +} +#endif // WITH_DEV_VIRTIO_9P diff --git a/dev/virtio/9p/client.c b/dev/virtio/9p/client.c new file mode 100644 index 0000000000..1fb3c8bed4 --- /dev/null +++ b/dev/virtio/9p/client.c @@ -0,0 +1,347 @@ +/* + * Copyright (c) 2023, Google Inc. All rights reserved. + * Author: codycswong@google.com (Cody Wong) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include + +#include "protocol.h" + +#define LOCAL_TRACE 0 + +static status_t pdu_init(struct p9_fcall *pdu, size_t size) +{ + vmm_alloc_contiguous(vmm_get_kernel_aspace(), "virtio_9p_pdu", size, + (void *)&pdu->sdata, 0, 0, + ARCH_MMU_FLAG_UNCACHED_DEVICE); + if (!pdu->sdata) + return ERR_NO_MEMORY; + pdu->capacity = size; + return NO_ERROR; +} + +static void pdu_fini(struct p9_fcall *pdu) +{ + if (pdu->sdata) + vmm_free_region(vmm_get_kernel_aspace(), (vaddr_t)pdu->sdata); + pdu->sdata = NULL; + pdu->capacity = 0; +} + +static void pdu_reset(struct p9_fcall *pdu) +{ + pdu->offset = 0; + pdu->size = 0; +} + +static status_t p9_req_prepare(struct p9_req *req, + const virtio_9p_msg_t *tmsg) +{ + struct virtio_9p_dev *p9dev = containerof(req, struct virtio_9p_dev, req); + status_t ret = NO_ERROR; + + if ((ret = pdu_init(&req->tc, p9dev->msize)) != NO_ERROR) { + goto err; + } + + if ((ret = pdu_init(&req->rc, p9dev->msize)) != NO_ERROR) { + goto err; + } + + pdu_reset(&req->tc); + pdu_reset(&req->rc); + + event_init(&req->io_event, false, EVENT_FLAG_AUTOUNSIGNAL); + req->status = P9_REQ_S_INITIALIZED; + + // fill 9p header + if (pdu_writed(&req->tc, 0) != NO_ERROR) { + ret = ERR_IO; + goto err; + } + if (pdu_writeb(&req->tc, tmsg->msg_type) != NO_ERROR) { + ret = ERR_IO; + goto err; + } + if (pdu_writew(&req->tc, tmsg->tag) != NO_ERROR) { + ret = ERR_IO; + goto err; + } + + return NO_ERROR; +err: + pdu_fini(&req->tc); + pdu_fini(&req->rc); + return ret; +} + +static void p9_req_release(struct p9_req *req) +{ + req->status = P9_REQ_S_UNKNOWN; + event_destroy(&req->io_event); + + pdu_fini(&req->tc); + pdu_fini(&req->rc); +} + +static status_t p9_req_finalize(struct p9_req *req) +{ + uint32_t size = req->tc.size; + status_t ret; + + pdu_reset(&req->tc); + ret = pdu_writed(&req->tc, size); + req->tc.size = size; +#if LOCAL_TRACE >= 2 + LTRACEF("req->tc.sdata (%p) size (%u)\n", req->tc.sdata, size); + hexdump8(req->tc.sdata, size); +#endif + + return ret; +} + +static void p9_req_receive(struct p9_req *req, + virtio_9p_msg_t *rmsg) +{ + pdu_readd(&req->rc); + rmsg->msg_type = pdu_readb(&req->rc); + rmsg->tag = pdu_readw(&req->rc); +#if LOCAL_TRACE >= 2 + LTRACEF("req->rc.sdata (%p) req->rc.size (%u)\n", req->rc.sdata, + req->rc.size); + hexdump8(req->rc.sdata, req->rc.size); +#endif +} + +static void virtio_9p_req_send(struct virtio_9p_dev *p9dev, + struct p9_req *req) +{ + struct virtio_device *dev = p9dev->dev; + struct vring_desc *desc; + uint16_t idx; + + spin_lock_saved_state_t state; + spin_lock_irqsave(&p9dev->lock, state); + + desc = virtio_alloc_desc_chain(dev, VIRTIO_9P_RING_IDX, 2, &idx); + + desc->len = req->tc.size; + desc->addr = vaddr_to_paddr(req->tc.sdata); + desc->flags |= VRING_DESC_F_NEXT; +#if LOCAL_TRACE > 2 + LTRACEF("desc (%p)\n", desc); + virtio_dump_desc(desc); +#endif + + desc = virtio_desc_index_to_desc(dev, VIRTIO_9P_RING_IDX, desc->next); + desc->len = req->rc.capacity; + desc->addr = vaddr_to_paddr(req->rc.sdata); + desc->flags |= VRING_DESC_F_WRITE; +#if LOCAL_TRACE > 2 + LTRACEF("desc (%p)\n", desc); + virtio_dump_desc(desc); +#endif + + req->status = P9_REQ_S_SENT; + + /* submit the transfer */ + virtio_submit_chain(dev, VIRTIO_9P_RING_IDX, idx); + + /* kick it off */ + virtio_kick(dev, VIRTIO_9P_RING_IDX); + + spin_unlock_irqrestore(&p9dev->lock, state); +} + +status_t virtio_9p_rpc(struct virtio_device *dev, const virtio_9p_msg_t *tmsg, + virtio_9p_msg_t *rmsg) +{ + LTRACEF("dev (%p) tmsg (%p) rmsg (%p)\n", dev, tmsg, rmsg); + + struct virtio_9p_dev *p9dev = dev->priv; + struct p9_req *req = &p9dev->req; + status_t ret; + + if (!tmsg || !rmsg) { + return ERR_INVALID_ARGS; + } + + // Since we allow only one outstanding request for now, we have a 9p device + // level lock for restricting only one rpc can be executed at a time. One + // day if we can support multiple outstanding requests, we should move the + // lock into the request allocation phase. + mutex_acquire(&p9dev->req_lock); + + // prepare the message header + ret = p9_req_prepare(req, tmsg); + if (ret != NO_ERROR) { + goto req_unlock; + } + + // setup the T-message by its msg-type + switch (tmsg->msg_type) { + case P9_TLOPEN: + ret = p9_proto_tlopen(req, tmsg); + break; + case P9_TGETATTR: + ret = p9_proto_tgetattr(req, tmsg); + break; + case P9_TVERSION: + ret = p9_proto_tversion(req, tmsg); + break; + case P9_TATTACH: + ret = p9_proto_tattach(req, tmsg); + break; + case P9_TWALK: + ret = p9_proto_twalk(req, tmsg); + break; + case P9_TOPEN: + ret = p9_proto_topen(req, tmsg); + break; + case P9_TREAD: + ret = p9_proto_tread(req, tmsg); + break; + case P9_TWRITE: + ret = p9_proto_twrite(req, tmsg); + break; + case P9_TCLUNK: + ret = p9_proto_tclunk(req, tmsg); + break; + case P9_TREMOVE: + ret = p9_proto_tremove(req, tmsg); + break; + case P9_TLCREATE: + ret = p9_proto_tlcreate(req, tmsg); + break; + case P9_TREADDIR: + ret = p9_proto_treaddir(req, tmsg); + break; + case P9_TMKDIR: + ret = p9_proto_tmkdir(req, tmsg); + break; + default: + LTRACEF("9p T-message type not supported: %u\n", tmsg->msg_type); + ret = ERR_NOT_SUPPORTED; + goto err; + } + + if (ret != NO_ERROR) { + LTRACEF("9p T-message (code: %u) failed: %d\n", tmsg->msg_type, ret); + goto err; + } + + if ((ret = p9_req_finalize(req)) != NO_ERROR) { + goto err; + } + + virtio_9p_req_send(p9dev, req); + + // wait for server's response + if (event_wait_timeout(&req->io_event, VIRTIO_9P_RPC_TIMEOUT) != NO_ERROR) { + ret = ERR_TIMED_OUT; + goto err; + } + + // read the message header from the returned request + p9_req_receive(req, rmsg); + + // read the R-message according to its msg-type + switch (rmsg->msg_type) { + case P9_RLOPEN: + ret = p9_proto_rlopen(req, rmsg); + break; + case P9_RGETATTR: + ret = p9_proto_rgetattr(req, rmsg); + break; + case P9_RVERSION: + ret = p9_proto_rversion(req, rmsg); + break; + case P9_RATTACH: + ret = p9_proto_rattach(req, rmsg); + break; + case P9_RWALK: + ret = p9_proto_rwalk(req, rmsg); + break; + case P9_ROPEN: + ret = p9_proto_ropen(req, rmsg); + break; + case P9_RREAD: + ret = p9_proto_rread(req, rmsg); + break; + case P9_RWRITE: + ret = p9_proto_rwrite(req, rmsg); + break; + case P9_RCLUNK: + ret = p9_proto_rclunk(req, rmsg); + break; + case P9_RREMOVE: + ret = p9_proto_rremove(req, rmsg); + break; + case P9_RLERROR: + ret = p9_proto_rlerror(req, rmsg); + break; + case P9_RLCREATE: + ret = p9_proto_rlcreate(req, rmsg); + break; + case P9_RREADDIR: + ret = p9_proto_rreaddir(req, rmsg); + break; + case P9_RMKDIR: + ret = p9_proto_rmkdir(req, rmsg); + break; + default: + LTRACEF("9p R-message type not supported: %u\n", tmsg->msg_type); + ret = ERR_NOT_SUPPORTED; + goto err; + } + +err: + p9_req_release(req); + +req_unlock: + mutex_release(&p9dev->req_lock); + + return ret; +} + +void virtio_9p_msg_destroy(virtio_9p_msg_t *msg) +{ + switch (msg->msg_type) { + case P9_RVERSION: + free(msg->msg.rversion.version); + break; + case P9_RREAD: + free(msg->msg.rread.data); + break; + case P9_RREADDIR: + free(msg->msg.rreaddir.data); + break; + default: + // didn't allocate extra space in the message + break; + } +} diff --git a/dev/virtio/9p/include/dev/virtio/9p.h b/dev/virtio/9p/include/dev/virtio/9p.h new file mode 100644 index 0000000000..829d0b3e62 --- /dev/null +++ b/dev/virtio/9p/include/dev/virtio/9p.h @@ -0,0 +1,461 @@ +/* + * Copyright (c) 2023, Google Inc. All rights reserved. + * Author: codycswong@google.com (Cody Wong) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#pragma once + +#include +#include +#include +#include +#include + +#define VIRTIO_9P_RING_IDX 0 +#define VIRTIO_9P_RING_SIZE 128 + +// Assuming there can be only one outstanding request. Pick a 16-bit unsigned +// value arbtrarily as the default tag for no good reason. +#define P9_TAG_DEFAULT ((uint16_t)0x15) +#define P9_TAG_NOTAG ((uint16_t)~0) + +#define P9_FID_NOFID ((uint32_t)~0) + +#define P9_UNAME_NONUNAME ((uint32_t)~0) + +#define P9_MAXWELEM 16 + +#ifdef V9P_HOST_DIR +#define V9P_MOUNT_ANAME V9P_HOST_DIR +#else +#define V9P_MOUNT_ANAME "" +#endif // V9P_MOUNT_ANAME + +// Linux file flags +#define O_RDONLY 00000000 +#define O_WRONLY 00000001 +#define O_RDWR 00000002 +#ifndef O_CREAT +#define O_CREAT 00000100 +#endif +#ifndef O_EXCL +#define O_EXCL 00000200 +#endif +#ifndef O_NOCTTY +#define O_NOCTTY 00000400 +#endif +#ifndef O_TRUNC +#define O_TRUNC 00001000 +#endif +#ifndef O_APPEND +#define O_APPEND 00002000 +#endif +#ifndef O_NONBLOCK +#define O_NONBLOCK 00004000 +#endif +#ifndef O_DSYNC +#define O_DSYNC 00010000 +#endif +#ifndef FASYNC +#define FASYNC 00020000 +#endif +#ifndef O_DIRECT +#define O_DIRECT 00040000 +#endif +#ifndef O_LARGEFILE +#define O_LARGEFILE 00100000 +#endif +#ifndef O_DIRECTORY +#define O_DIRECTORY 00200000 +#endif +#ifndef O_NOFOLLOW +#define O_NOFOLLOW 00400000 +#endif +#ifndef O_NOATIME +#define O_NOATIME 01000000 +#endif +#ifndef O_CLOEXEC +#define O_CLOEXEC 02000000 +#endif + +// POSIX file modes +#define S_IRWXU 00700 // user (file owner) has read, write, and execute permission +#define S_IRUSR 00400 // user has read permission +#define S_IWUSR 00200 // user has write permission +#define S_IXUSR 00100 // user has execute permission +#define S_IRWXG 00070 // group has read, write, and execute permission +#define S_IRGRP 00040 // group has read permission +#define S_IWGRP 00020 // group has write permission +#define S_IXGRP 00010 // group has execute permission +#define S_IRWXO 00007 // others have read, write, and execute permission +#define S_IROTH 00004 // others have read permission +#define S_IWOTH 00002 // others have write permission +#define S_IXOTH 00001 // others have execute permission +/* According to POSIX, the effect when other bits are set in mode is + * unspecified. On Linux, the following bits are also honored in mode: */ +#define S_ISUID 0004000 // set-user-ID bit +#define S_ISGID 0002000 // set-group-ID bit (see inode(7)). +#define S_ISVTX 0001000 // sticky bit (see inode(7)). + +// Linux file modes +#define S_IFMT 00170000 +#define S_IFSOCK 0140000 +#define S_IFLNK 0120000 +#define S_IFREG 0100000 +#define S_IFBLK 0060000 +#define S_IFDIR 0040000 +#define S_IFCHR 0020000 +#define S_IFIFO 0010000 +#define S_ISUID 0004000 +#define S_ISGID 0002000 +#define S_ISVTX 0001000 + +#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) +#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) +#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) +#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) +#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) +#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) + +#define P9_GETATTR_MODE 0x00000001ULL +#define P9_GETATTR_NLINK 0x00000002ULL +#define P9_GETATTR_UID 0x00000004ULL +#define P9_GETATTR_GID 0x00000008ULL +#define P9_GETATTR_RDEV 0x00000010ULL +#define P9_GETATTR_ATIME 0x00000020ULL +#define P9_GETATTR_MTIME 0x00000040ULL +#define P9_GETATTR_CTIME 0x00000080ULL +#define P9_GETATTR_INO 0x00000100ULL +#define P9_GETATTR_SIZE 0x00000200ULL +#define P9_GETATTR_BLOCKS 0x00000400ULL + +#define P9_GETATTR_BTIME 0x00000800ULL +#define P9_GETATTR_GEN 0x00001000ULL +#define P9_GETATTR_DATA_VERSION 0x00002000ULL + +#define P9_GETATTR_BASIC 0x000007ffULL /* Mask for fields up to BLOCKS */ +#define P9_GETATTR_ALL 0x00003fffULL /* Mask for All fields above */ + +enum virtio_9p_msg_type_t { + P9_TLERROR = 6, + P9_RLERROR, + P9_TSTATFS = 8, + P9_RSTATFS, + P9_TLOPEN = 12, + P9_RLOPEN, + P9_TLCREATE = 14, + P9_RLCREATE, + P9_TSYMLINK = 16, + P9_RSYMLINK, + P9_TMKNOD = 18, + P9_RMKNOD, + P9_TRENAME = 20, + P9_RRENAME, + P9_TREADLINK = 22, + P9_RREADLINK, + P9_TGETATTR = 24, + P9_RGETATTR, + P9_TSETATTR = 26, + P9_RSETATTR, + P9_TXATTRWALK = 30, + P9_RXATTRWALK, + P9_TXATTRCREATE = 32, + P9_RXATTRCREATE, + P9_TREADDIR = 40, + P9_RREADDIR, + P9_TFSYNC = 50, + P9_RFSYNC, + P9_TLOCK = 52, + P9_RLOCK, + P9_TGETLOCK = 54, + P9_RGETLOCK, + P9_TLINK = 70, + P9_RLINK, + P9_TMKDIR = 72, + P9_RMKDIR, + P9_TRENAMEAT = 74, + P9_RRENAMEAT, + P9_TUNLINKAT = 76, + P9_RUNLINKAT, + P9_TVERSION = 100, + P9_RVERSION, + P9_TAUTH = 102, + P9_RAUTH, + P9_TATTACH = 104, + P9_RATTACH, + P9_TERROR = 106, + P9_RERROR, + P9_TFLUSH = 108, + P9_RFLUSH, + P9_TWALK = 110, + P9_RWALK, + P9_TOPEN = 112, + P9_ROPEN, + P9_TCREATE = 114, + P9_RCREATE, + P9_TREAD = 116, + P9_RREAD, + P9_TWRITE = 118, + P9_RWRITE, + P9_TCLUNK = 120, + P9_RCLUNK, + P9_TREMOVE = 122, + P9_RREMOVE, + P9_TSTAT = 124, + P9_RSTAT, + P9_TWSTAT = 126, + P9_RWSTAT, +}; + +typedef struct _virtio_9p_qid_t { + uint8_t type; + uint32_t version; + uint64_t path; +} virtio_9p_qid_t; + +enum p9_qid_t { + P9_QTDIR = 0x80, + P9_QTAPPEND = 0x40, + P9_QTEXCL = 0x20, + P9_QTMOUNT = 0x10, + P9_QTAUTH = 0x08, + P9_QTTMP = 0x04, + P9_QTSYMLINK = 0x02, + P9_QTLINK = 0x01, + P9_QTFILE = 0x00, +}; + +typedef struct p9_dirent { + virtio_9p_qid_t qid; + uint64_t offset; + uint8_t type; + char *name; +} p9_dirent_t; + +typedef struct _virtio_9p_msg_t { + enum virtio_9p_msg_type_t msg_type; + uint16_t tag; + + // the 9p message structures supporting 9P2000.L + union { + // Tlerror + // Rlerror + struct { + uint32_t ecode; + } rlerror; + + // Tlopen + struct { + uint32_t fid; + uint32_t flags; + } tlopen; + // Rlopen + struct { + virtio_9p_qid_t qid; + uint32_t iounit; + } rlopen; + + // Tlcreate + struct { + uint32_t fid; + const char *name; + uint32_t flags; + uint32_t mode; + uint32_t gid; + } tlcreate; + // Rlcreate + struct { + virtio_9p_qid_t qid; + uint32_t iounit; + } rlcreate; + + // Tgetattr + struct { + uint32_t fid; + uint64_t request_mask; + } tgetattr; + // Rgetattr + struct { + uint64_t valid; + virtio_9p_qid_t qid; + uint32_t mode; + uint32_t uid; + uint32_t gid; + uint64_t nlink; + uint64_t rdev; + uint64_t size; + uint64_t blksize; + uint64_t blocks; + uint64_t atime_sec; + uint64_t atime_nsec; + uint64_t mtime_sec; + uint64_t mtime_nsec; + uint64_t ctime_sec; + uint64_t ctime_nsec; + uint64_t btime_sec; + uint64_t btime_nsec; + uint64_t gen; + uint64_t data_version; + } rgetattr; + + // Tversion + struct { + uint32_t msize; + const char *version; + } tversion; + // Rversion + struct { + uint32_t msize; + char *version; + } rversion; + + // Tauth + // Rauth + + // Rerror + + // Tflush + // Rflush + + // Tattach + struct { + uint32_t fid; + uint32_t afid; + const char *uname; + const char *aname; + uint32_t n_uname; + } tattach; + // Rattach + struct { + virtio_9p_qid_t qid; + } rattach; + + // Twalk + struct { + uint32_t fid; + uint32_t newfid; + uint16_t nwname; + const char *wname[P9_MAXWELEM]; + } twalk; + // Rwalk + struct { + uint16_t nwqid; + virtio_9p_qid_t qid[P9_MAXWELEM]; + } rwalk; + + // Topen + struct { + uint32_t fid; + uint8_t mode; + } topen; + // Ropen + struct { + virtio_9p_qid_t qid; + uint32_t iounit; + } ropen; + + // Tcreate + // Rcreate + + // Tread + struct { + uint32_t fid; + uint64_t offset; + uint32_t count; + } tread; + // Rread + struct { + uint32_t count; + uint8_t *data; + } rread; + + // Twrite + struct { + uint32_t fid; + uint64_t offset; + uint32_t count; + const uint8_t *data; + } twrite; + // Rwrite + struct { + uint32_t count; + } rwrite; + + // Tclunk + struct { + uint32_t fid; + } tclunk; + // Rclunk + struct { + } rclunk; + + // Tremove + struct { + uint32_t fid; + } tremove; + // Rremove + struct { + } rremove; + + // Tstat + // Rstat + + // Twstat + // Rwstat + + // Treaddir + struct { + uint32_t fid; + uint64_t offset; + uint32_t count; + } treaddir; + // Rreaddir + struct { + uint32_t count; + uint8_t *data; + } rreaddir; + + // Tmkdir + struct { + uint32_t dfid; + const char *name; + uint32_t mode; + uint32_t gid; + } tmkdir; + // Rmkdir + struct { + virtio_9p_qid_t qid; + } rmkdir; + } msg; + +} virtio_9p_msg_t; + +status_t virtio_9p_init(struct virtio_device *dev, uint32_t host_features) __NONNULL(); +status_t virtio_9p_start(struct virtio_device *dev) __NONNULL(); +struct virtio_device *virtio_9p_bdev_to_virtio_device(bdev_t *bdev); +struct virtio_device *virtio_get_9p_device(uint index); + +status_t virtio_9p_rpc(struct virtio_device *dev, const virtio_9p_msg_t *tmsg, + virtio_9p_msg_t *rmsg); +void virtio_9p_msg_destroy(virtio_9p_msg_t *msg); +ssize_t p9_dirent_read(uint8_t *data, uint32_t size, p9_dirent_t *ent); +void p9_dirent_destroy(p9_dirent_t *ent); diff --git a/dev/virtio/9p/protocol.c b/dev/virtio/9p/protocol.c new file mode 100644 index 0000000000..b00e6bff54 --- /dev/null +++ b/dev/virtio/9p/protocol.c @@ -0,0 +1,606 @@ +/* + * Copyright (c) 2023, Google Inc. All rights reserved. + * Author: codycswong@google.com (Cody Wong) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "protocol.h" + +#include +#include +#include +#include +#include + +#define LOCAL_TRACE 0 + +/* + * read/write of basic type APIs + */ + +size_t pdu_read(struct p9_fcall *pdu, void *data, size_t size) +{ + size_t len = MIN(pdu->size - pdu->offset, size); + + memcpy(data, &pdu->sdata[pdu->offset], len); + pdu->offset += len; + return len; +} + +size_t pdu_write(struct p9_fcall *pdu, void *data, size_t size) +{ + size_t len = MIN(pdu->capacity - pdu->offset, size); + + memcpy(&pdu->sdata[pdu->size], data, len); + pdu->size += len; + return len; +} + +status_t pdu_writeb(struct p9_fcall *pdu, uint8_t byte) +{ + return pdu_write(pdu, &byte, 1) == 1 ? NO_ERROR : ERR_IO; +} + +uint8_t pdu_readb(struct p9_fcall *pdu) +{ + uint8_t byte; + ASSERT(pdu_read(pdu, &byte, 1) == 1); + return byte; +} + +status_t pdu_writew(struct p9_fcall *pdu, uint16_t word) +{ + word = LE16(word); + return pdu_write(pdu, &word, 2) == 2 ? NO_ERROR : ERR_IO; +} + +uint16_t pdu_readw(struct p9_fcall *pdu) +{ + uint16_t word; + ASSERT(pdu_read(pdu, &word, 2) == 2); + return LE16(word); +} + +status_t pdu_writed(struct p9_fcall *pdu, uint32_t dword) +{ + dword = LE32(dword); + return pdu_write(pdu, &dword, 4) == 4 ? NO_ERROR : ERR_IO; +} + +uint32_t pdu_readd(struct p9_fcall *pdu) +{ + uint32_t dword; + ASSERT(pdu_read(pdu, &dword, 4) == 4); + return LE32(dword); +} + +status_t pdu_writeq(struct p9_fcall *pdu, uint64_t qword) +{ + qword = LE64(qword); + return pdu_write(pdu, &qword, 8) == 8 ? NO_ERROR : ERR_IO; +} + +uint64_t pdu_readq(struct p9_fcall *pdu) +{ + uint64_t qword; + ASSERT(pdu_read(pdu, &qword, 8) == 8); + return LE64(qword); +} + +status_t pdu_writestr(struct p9_fcall *pdu, const char *str) +{ + uint16_t len = strlen(str); + status_t ret; + + if ((ret = pdu_writew(pdu, len)) != NO_ERROR) + return ret; + + return pdu_write(pdu, (void *)str, len) == len ? NO_ERROR : ERR_IO; +} + +char *pdu_readstr(struct p9_fcall *pdu) +{ + uint16_t len; + char *str = NULL; + + len = pdu_readw(pdu); + if (!len) { + return NULL; + } + + str = calloc(len + 1, sizeof(char)); + if (!str) { + return NULL; + } + + ASSERT(pdu_read(pdu, str, len) == len); + return str; +} + +virtio_9p_qid_t pdu_readqid(struct p9_fcall *pdu) +{ + virtio_9p_qid_t qid; + qid.type = pdu_readb(pdu); + qid.version = pdu_readd(pdu); + qid.path = pdu_readq(pdu); + return qid; +} + +status_t pdu_writedata(struct p9_fcall *pdu, const uint8_t *data, + uint32_t count) +{ + status_t ret; +#if LOCAL_TRACE >= 2 + LTRACEF("count (%u) data (%p)\n", count, data); + hexdump8(data, count); +#endif + + if ((ret = pdu_writed(pdu, count)) != NO_ERROR) + return ret; + + return pdu_write(pdu, (void *)data, count) == count ? NO_ERROR : ERR_IO; +} + +uint8_t *pdu_readdata(struct p9_fcall *pdu, uint32_t *count) +{ + uint8_t *data = NULL; + + *count = pdu_readd(pdu); + if (*count == 0) + return NULL; + + data = calloc(*count, sizeof(uint8_t)); + if (!data) + return NULL; + + ASSERT(pdu_read(pdu, data, *count) == *count); +#if LOCAL_TRACE >= 2 + LTRACEF("count (%u) data (%p)\n", *count, data); + hexdump8(data, *count); +#endif + return data; +} + +ssize_t p9_dirent_read(uint8_t *data, uint32_t size, p9_dirent_t *ent) +{ + struct p9_fcall fake_pdu; + + fake_pdu.sdata = data; + fake_pdu.size = size; + fake_pdu.capacity = size; + fake_pdu.offset = 0; + + // Rreaddir pattern: qid[13] offset[8] type[1] name[s] + ent->qid = pdu_readqid(&fake_pdu); + ent->offset = pdu_readq(&fake_pdu); + ent->type = pdu_readb(&fake_pdu); + ent->name = pdu_readstr(&fake_pdu); + + LTRACEF( + "9p read_dirent: qid.type (0x%x) qid.version (%u) qid.path (%llu) " + "offset (%llu) type (0x%x) name (%s)\n", + ent->qid.type, ent->qid.version, ent->qid.path, ent->offset, ent->type, + ent->name); + + return fake_pdu.offset; +} + +void p9_dirent_destroy(p9_dirent_t *ent) +{ + if (ent) { + if (ent->name) { + free(ent->name); + ent->name = NULL; + } + } +} + +/* + * Plan 9 File Protocol APIs + */ + +status_t p9_proto_tversion(struct p9_req *req, const virtio_9p_msg_t *tmsg) +{ + status_t ret; + + // Tversion pattern: msize[4] version[s] + if ((ret = pdu_writed(&req->tc, tmsg->msg.tversion.msize)) != NO_ERROR) + return ret; + if ((ret = pdu_writestr(&req->tc, tmsg->msg.tversion.version)) != NO_ERROR) + return ret; + + return NO_ERROR; +} + +status_t p9_proto_rversion(struct p9_req *req, virtio_9p_msg_t *rmsg) +{ + // Rversion pattern: msize[4] version[s] + rmsg->msg.rversion.msize = pdu_readd(&req->rc); + rmsg->msg.rversion.version = pdu_readstr(&req->rc); + + LTRACEF("9p version: msize (%u), version (%s)\n", rmsg->msg.rversion.msize, + rmsg->msg.rversion.version); + + return NO_ERROR; +} + +status_t p9_proto_tattach(struct p9_req *req, const virtio_9p_msg_t *tmsg) +{ + status_t ret; + + // Tattach pattern: fid[4] afid[4] uname[s] aname[s] n_uname[4] + if ((ret = pdu_writed(&req->tc, tmsg->msg.tattach.fid)) != NO_ERROR) + return ret; + if ((ret = pdu_writed(&req->tc, tmsg->msg.tattach.afid)) != NO_ERROR) + return ret; + if ((ret = pdu_writestr(&req->tc, tmsg->msg.tattach.uname)) != NO_ERROR) + return ret; + if ((ret = pdu_writestr(&req->tc, tmsg->msg.tattach.aname)) != NO_ERROR) + return ret; + if ((ret = pdu_writed(&req->tc, tmsg->msg.tattach.n_uname)) != NO_ERROR) + return ret; + + return NO_ERROR; +} + +status_t p9_proto_rattach(struct p9_req *req, virtio_9p_msg_t *rmsg) +{ + // Rattach pattern: qid[13] + rmsg->msg.rattach.qid = pdu_readqid(&req->rc); + + LTRACEF("9p attach: type (0x%x), version (%u), path (%llu)\n", + rmsg->msg.rattach.qid.type, rmsg->msg.rattach.qid.version, + rmsg->msg.rattach.qid.path); + + return NO_ERROR; +} + +status_t p9_proto_twalk(struct p9_req *req, const virtio_9p_msg_t *tmsg) +{ + status_t ret; + + // Twalk pattern: fid[4] newfid[4] nwname[2] nwname*(wname[s]) + if ((ret = pdu_writed(&req->tc, tmsg->msg.twalk.fid)) != NO_ERROR) + return ret; + if ((ret = pdu_writed(&req->tc, tmsg->msg.twalk.newfid)) != NO_ERROR) + return ret; + if ((ret = pdu_writew(&req->tc, tmsg->msg.twalk.nwname)) != NO_ERROR) + return ret; + for (int i = 0; i < tmsg->msg.twalk.nwname; i++) { + if ((ret = pdu_writestr(&req->tc, tmsg->msg.twalk.wname[i])) != NO_ERROR) + return ret; + } + + return NO_ERROR; +} + +status_t p9_proto_rwalk(struct p9_req *req, virtio_9p_msg_t *rmsg) +{ + // Rwalk pattern: nwqid[2] nwqid*(qid[13]) + rmsg->msg.rwalk.nwqid = pdu_readw(&req->rc); + + for (int i = 0; i < rmsg->msg.rwalk.nwqid; i++) { + rmsg->msg.rwalk.qid[i] = pdu_readqid(&req->rc); + LTRACEF("9p walk: type (0x%x), version (%u), path (%llu)\n", + rmsg->msg.rwalk.qid[i].type, rmsg->msg.rwalk.qid[i].version, + rmsg->msg.rwalk.qid[i].path); + } + + return NO_ERROR; +} + +status_t p9_proto_topen(struct p9_req *req, const virtio_9p_msg_t *tmsg) +{ + status_t ret; + + // Topen pattern: fid[4] mode[1] + if ((ret = pdu_writed(&req->tc, tmsg->msg.topen.fid)) != NO_ERROR) + return ret; + if ((ret = pdu_writeb(&req->tc, tmsg->msg.topen.mode)) != NO_ERROR) + return ret; + + return NO_ERROR; +} + +status_t p9_proto_ropen(struct p9_req *req, virtio_9p_msg_t *rmsg) +{ + // Ropen pattern: qid[13] iounit[4] + rmsg->msg.ropen.qid = pdu_readqid(&req->rc); + rmsg->msg.ropen.iounit = pdu_readd(&req->rc); + + LTRACEF("9p open: type (0x%x), version (%u), path (%llu), iounit (%u)\n", + rmsg->msg.ropen.qid.type, rmsg->msg.ropen.qid.version, + rmsg->msg.ropen.qid.path, rmsg->msg.ropen.iounit); + + return NO_ERROR; +} + +status_t p9_proto_tlopen(struct p9_req *req, const virtio_9p_msg_t *tmsg) +{ + status_t ret; + + // Tlopen pattern: fid[4] flags[4] + if ((ret = pdu_writed(&req->tc, tmsg->msg.tlopen.fid)) != NO_ERROR) + return ret; + if ((ret = pdu_writed(&req->tc, tmsg->msg.tlopen.flags)) != NO_ERROR) + return ret; + + return NO_ERROR; +} + +status_t p9_proto_rlopen(struct p9_req *req, virtio_9p_msg_t *rmsg) +{ + // Rlopen pattern: qid[13] iounit[4] + rmsg->msg.rlopen.qid = pdu_readqid(&req->rc); + rmsg->msg.rlopen.iounit = pdu_readd(&req->rc); + + LTRACEF("9p lopen: type (0x%x), version (%u), path (%llu), iounit (%u)\n", + rmsg->msg.rlopen.qid.type, rmsg->msg.rlopen.qid.version, + rmsg->msg.rlopen.qid.path, rmsg->msg.rlopen.iounit); + + return NO_ERROR; +} + +status_t p9_proto_tgetattr(struct p9_req *req, const virtio_9p_msg_t *tmsg) +{ + status_t ret; + + // Tgetattr pattern: fid[4] request_mask[8] + if ((ret = pdu_writed(&req->tc, tmsg->msg.tgetattr.fid)) != NO_ERROR) + return ret; + if ((ret = pdu_writeq(&req->tc, tmsg->msg.tgetattr.request_mask)) != NO_ERROR) + return ret; + + return NO_ERROR; +} + +status_t p9_proto_rgetattr(struct p9_req *req, virtio_9p_msg_t *rmsg) +{ + // Rgetattr pattern: valid[8] qid[13] mode[4] uid[4] gid[4] nlink[8] + // rdev[8] size[8] blksize[8] blocks[8] + // atime_sec[8] atime_nsec[8] mtime_sec[8] mtime_nsec[8] + // ctime_sec[8] ctime_nsec[8] btime_sec[8] btime_nsec[8] + // gen[8] data_version[8] + + rmsg->msg.rgetattr.valid = pdu_readq(&req->rc); + rmsg->msg.rgetattr.qid = pdu_readqid(&req->rc); + rmsg->msg.rgetattr.mode = pdu_readd(&req->rc); + rmsg->msg.rgetattr.uid = pdu_readd(&req->rc); + rmsg->msg.rgetattr.gid = pdu_readd(&req->rc); + rmsg->msg.rgetattr.nlink = pdu_readq(&req->rc); + rmsg->msg.rgetattr.rdev = pdu_readq(&req->rc); + rmsg->msg.rgetattr.size = pdu_readq(&req->rc); + rmsg->msg.rgetattr.blksize = pdu_readq(&req->rc); + rmsg->msg.rgetattr.blocks = pdu_readq(&req->rc); + rmsg->msg.rgetattr.atime_sec = pdu_readq(&req->rc); + rmsg->msg.rgetattr.atime_nsec = pdu_readq(&req->rc); + rmsg->msg.rgetattr.mtime_sec = pdu_readq(&req->rc); + rmsg->msg.rgetattr.mtime_nsec = pdu_readq(&req->rc); + rmsg->msg.rgetattr.ctime_sec = pdu_readq(&req->rc); + rmsg->msg.rgetattr.ctime_nsec = pdu_readq(&req->rc); + rmsg->msg.rgetattr.btime_sec = pdu_readq(&req->rc); + rmsg->msg.rgetattr.btime_nsec = pdu_readq(&req->rc); + rmsg->msg.rgetattr.gen = pdu_readq(&req->rc); + rmsg->msg.rgetattr.data_version = pdu_readq(&req->rc); + + LTRACEF( + "9p getattr: valid (0x%llx), qid.type (0x%x), qid.version (%u), " + "qid.path (%llu), mode (0x%x), uid (%u), gid (%u), nlink (%llu), rdev " + "(%llu), size (%llu), blksize (%llu), blocks (%llu), atime_sec (%llu), " + "atime_nsec (%llu), mtime_sec (%llu), mtime_nsec (%llu), ctime_sec " + "(%llu), ctime_nsec (%llu), btime_sec (%llu), btime_nsec (%llu), gen " + "(%llu), data_version (%llu)\n", + rmsg->msg.rgetattr.valid, rmsg->msg.rgetattr.qid.type, + rmsg->msg.rgetattr.qid.version, rmsg->msg.rgetattr.qid.path, + rmsg->msg.rgetattr.mode, rmsg->msg.rgetattr.uid, rmsg->msg.rgetattr.gid, + rmsg->msg.rgetattr.nlink, rmsg->msg.rgetattr.rdev, + rmsg->msg.rgetattr.size, rmsg->msg.rgetattr.blksize, + rmsg->msg.rgetattr.blocks, rmsg->msg.rgetattr.atime_sec, + rmsg->msg.rgetattr.atime_nsec, rmsg->msg.rgetattr.mtime_sec, + rmsg->msg.rgetattr.mtime_nsec, rmsg->msg.rgetattr.ctime_sec, + rmsg->msg.rgetattr.ctime_nsec, rmsg->msg.rgetattr.btime_sec, + rmsg->msg.rgetattr.btime_nsec, rmsg->msg.rgetattr.gen, + rmsg->msg.rgetattr.data_version); + + return NO_ERROR; +} + +status_t p9_proto_tread(struct p9_req *req, const virtio_9p_msg_t *tmsg) +{ + status_t ret; + + // Tread pattern: fid[4] offset[8] count[4] + if ((ret = pdu_writed(&req->tc, tmsg->msg.tread.fid)) != NO_ERROR) + return ret; + if ((ret = pdu_writeq(&req->tc, tmsg->msg.tread.offset)) != NO_ERROR) + return ret; + if ((ret = pdu_writed(&req->tc, tmsg->msg.tread.count)) != NO_ERROR) + return ret; + + return NO_ERROR; +} + +status_t p9_proto_rread(struct p9_req *req, virtio_9p_msg_t *rmsg) +{ + // Rread pattern: count[4] data[count] + rmsg->msg.rread.data = pdu_readdata(&req->rc, &rmsg->msg.rread.count); + + LTRACEF("9p read: count (%u) data (%p)\n", rmsg->msg.rread.count, rmsg->msg.rread.data); + + return NO_ERROR; +} + +status_t p9_proto_twrite(struct p9_req *req, const virtio_9p_msg_t *tmsg) +{ + status_t ret; + + // Twrite pattern: fid[4] offset[8] count[4] data[count] + if ((ret = pdu_writed(&req->tc, tmsg->msg.twrite.fid)) != NO_ERROR) + return ret; + if ((ret = pdu_writeq(&req->tc, tmsg->msg.twrite.offset)) != NO_ERROR) + return ret; + if ((ret = pdu_writedata(&req->tc, tmsg->msg.twrite.data, tmsg->msg.twrite.count)) != NO_ERROR) + return ret; + + return NO_ERROR; +} + +status_t p9_proto_rwrite(struct p9_req *req, virtio_9p_msg_t *rmsg) +{ + // Rwrite pattern: count[4] + rmsg->msg.rwrite.count = pdu_readd(&req->rc); + + LTRACEF("9p write: count %u\n", rmsg->msg.rwrite.count); + + return NO_ERROR; +} + +status_t p9_proto_tclunk(struct p9_req *req, const virtio_9p_msg_t *tmsg) +{ + status_t ret; + + // Tclunk pattern: fid[4] + if ((ret = pdu_writed(&req->tc, tmsg->msg.tclunk.fid)) != NO_ERROR) + return ret; + + return NO_ERROR; +} + +status_t p9_proto_rclunk(struct p9_req *req, virtio_9p_msg_t *rmsg) +{ + // Rclunk pattern: + + return NO_ERROR; +} + +status_t p9_proto_tremove(struct p9_req *req, const virtio_9p_msg_t *tmsg) +{ + status_t ret; + + // Tremove pattern: fid[4] + if ((ret = pdu_writed(&req->tc, tmsg->msg.tremove.fid)) != NO_ERROR) + return ret; + + return NO_ERROR; +} + +status_t p9_proto_rremove(struct p9_req *req, virtio_9p_msg_t *rmsg) +{ + // Rremove pattern: + + return NO_ERROR; +} + +status_t p9_proto_rlerror(struct p9_req *req, virtio_9p_msg_t *rmsg) +{ + // Rlerror pattern: ecode[4] + rmsg->msg.rlerror.ecode = pdu_readd(&req->rc); + + LTRACEF("9p lerror: ecode %u\n", rmsg->msg.rlerror.ecode); + + return NO_ERROR; +} + +status_t p9_proto_tlcreate(struct p9_req *req, const virtio_9p_msg_t *tmsg) +{ + status_t ret; + + // Tlcreate pattern: fid[4] name[s] flags[4] mode[4] gid[4] + if ((ret = pdu_writed(&req->tc, tmsg->msg.tlcreate.fid)) != NO_ERROR) + return ret; + if ((ret = pdu_writestr(&req->tc, tmsg->msg.tlcreate.name)) != NO_ERROR) + return ret; + if ((ret = pdu_writed(&req->tc, tmsg->msg.tlcreate.flags)) != NO_ERROR) + return ret; + if ((ret = pdu_writed(&req->tc, tmsg->msg.tlcreate.mode)) != NO_ERROR) + return ret; + if ((ret = pdu_writed(&req->tc, tmsg->msg.tlcreate.gid)) != NO_ERROR) + return ret; + + return NO_ERROR; +} + +status_t p9_proto_rlcreate(struct p9_req *req, virtio_9p_msg_t *rmsg) +{ + // Rlcreate pattern: qid[13] iounit[4] + rmsg->msg.rlcreate.qid = pdu_readqid(&req->rc); + rmsg->msg.rlcreate.iounit = pdu_readd(&req->rc); + + LTRACEF("9p lcreate: type (0x%x), version (%u), path (%llu), iounit (%u)\n", + rmsg->msg.rlcreate.qid.type, rmsg->msg.rlcreate.qid.version, + rmsg->msg.rlcreate.qid.path, rmsg->msg.rlcreate.iounit); + + return NO_ERROR; +} + +status_t p9_proto_treaddir(struct p9_req *req, const virtio_9p_msg_t *tmsg) +{ + status_t ret; + + // Treaddir pattern: fid[4] offset[8] count[4] + if ((ret = pdu_writed(&req->tc, tmsg->msg.treaddir.fid)) != NO_ERROR) + return ret; + if ((ret = pdu_writeq(&req->tc, tmsg->msg.treaddir.offset)) != NO_ERROR) + return ret; + if ((ret = pdu_writed(&req->tc, tmsg->msg.treaddir.count)) != NO_ERROR) + return ret; + + return NO_ERROR; +} + +status_t p9_proto_rreaddir(struct p9_req *req, virtio_9p_msg_t *rmsg) +{ + // Rreaddir pattern: count[4] data[count] + rmsg->msg.rreaddir.data = pdu_readdata(&req->rc, &rmsg->msg.rreaddir.count); + + LTRACEF("9p readdir: count (%u) data (%p)\n", rmsg->msg.rreaddir.count, + rmsg->msg.rreaddir.data); + + return NO_ERROR; +} + +status_t p9_proto_tmkdir(struct p9_req *req, const virtio_9p_msg_t *tmsg) +{ + status_t ret; + + // Tmkdir pattern: dfid[4] name[s] mode[4] gid[4] + if ((ret = pdu_writed(&req->tc, tmsg->msg.tmkdir.dfid)) != NO_ERROR) + return ret; + if ((ret = pdu_writestr(&req->tc, tmsg->msg.tmkdir.name)) != NO_ERROR) + return ret; + if ((ret = pdu_writed(&req->tc, tmsg->msg.tmkdir.mode)) != NO_ERROR) + return ret; + if ((ret = pdu_writed(&req->tc, tmsg->msg.tmkdir.gid)) != NO_ERROR) + return ret; + + return NO_ERROR; +} + +status_t p9_proto_rmkdir(struct p9_req *req, virtio_9p_msg_t *rmsg) +{ + // Rmkdir pattern: qid[13] + rmsg->msg.rmkdir.qid = pdu_readqid(&req->rc); + + LTRACEF("9p mkdir: type (0x%x), version (%u), path (%llu)\n", + rmsg->msg.rmkdir.qid.type, rmsg->msg.rmkdir.qid.version, + rmsg->msg.rmkdir.qid.path); + + return NO_ERROR; +} diff --git a/dev/virtio/9p/protocol.h b/dev/virtio/9p/protocol.h new file mode 100644 index 0000000000..0a52a750e9 --- /dev/null +++ b/dev/virtio/9p/protocol.h @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2023, Google Inc. All rights reserved. + * Author: codycswong@google.com (Cody Wong) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#pragma once + +#include +#include +#include +#include +#include +#include + +#define VIRTIO_9P_RPC_TIMEOUT 3000 /* ms */ +#define VIRTIO_9P_DEFAULT_MSIZE (PAGE_SIZE << 5) + +struct p9_fcall { + uint32_t size; + + size_t offset; + size_t capacity; + + uint8_t *sdata; +}; + +struct p9_req { + int status; + event_t io_event; + struct p9_fcall tc; + struct p9_fcall rc; +}; + +enum { + P9_REQ_S_UNKNOWN = 0, + P9_REQ_S_INITIALIZED, + P9_REQ_S_SENT, + P9_REQ_S_RECEIVED, +}; + +struct virtio_9p_dev { + struct virtio_device *dev; + struct virtio_9p_config *config; + bdev_t bdev; + + uint32_t msize; + struct p9_req req; + mutex_t req_lock; + + struct list_node list; + spin_lock_t lock; +}; + +// read/write APIs of basic types +size_t pdu_read(struct p9_fcall *pdu, void *data, size_t size); +size_t pdu_write(struct p9_fcall *pdu, void *data, size_t size); +status_t pdu_writeb(struct p9_fcall *pdu, uint8_t byte); +uint8_t pdu_readb(struct p9_fcall *pdu); +status_t pdu_writew(struct p9_fcall *pdu, uint16_t word); +uint16_t pdu_readw(struct p9_fcall *pdu); +status_t pdu_writed(struct p9_fcall *pdu, uint32_t dword); +uint32_t pdu_readd(struct p9_fcall *pdu); +status_t pdu_writeq(struct p9_fcall *pdu, uint64_t qword); +uint64_t pdu_readq(struct p9_fcall *pdu); +status_t pdu_writestr(struct p9_fcall *pdu, const char *str); +char *pdu_readstr(struct p9_fcall *pdu); +virtio_9p_qid_t pdu_readqid(struct p9_fcall *pdu); +status_t pdu_writedata(struct p9_fcall *pdu, const uint8_t *data, uint32_t count); +uint8_t *pdu_readdata(struct p9_fcall *pdu, uint32_t *count); + +// Plan 9 File Protocol APIs +status_t p9_proto_tversion(struct p9_req *req, const virtio_9p_msg_t *tmsg); +status_t p9_proto_rversion(struct p9_req *req, virtio_9p_msg_t *rmsg); +status_t p9_proto_tattach(struct p9_req *req, const virtio_9p_msg_t *tmsg); +status_t p9_proto_rattach(struct p9_req *req, virtio_9p_msg_t *rmsg); +status_t p9_proto_twalk(struct p9_req *req, const virtio_9p_msg_t *tmsg); +status_t p9_proto_rwalk(struct p9_req *req, virtio_9p_msg_t *rmsg); +status_t p9_proto_topen(struct p9_req *req, const virtio_9p_msg_t *tmsg); +status_t p9_proto_ropen(struct p9_req *req, virtio_9p_msg_t *rmsg); +status_t p9_proto_tlopen(struct p9_req *req, const virtio_9p_msg_t *tmsg); +status_t p9_proto_rlopen(struct p9_req *req, virtio_9p_msg_t *rmsg); +status_t p9_proto_tgetattr(struct p9_req *req, const virtio_9p_msg_t *tmsg); +status_t p9_proto_rgetattr(struct p9_req *req, virtio_9p_msg_t *rmsg); +status_t p9_proto_tread(struct p9_req *req, const virtio_9p_msg_t *tmsg); +status_t p9_proto_rread(struct p9_req *req, virtio_9p_msg_t *rmsg); +status_t p9_proto_twrite(struct p9_req *req, const virtio_9p_msg_t *tmsg); +status_t p9_proto_rwrite(struct p9_req *req, virtio_9p_msg_t *rmsg); +status_t p9_proto_tclunk(struct p9_req *req, const virtio_9p_msg_t *tmsg); +status_t p9_proto_rclunk(struct p9_req *req, virtio_9p_msg_t *rmsg); +status_t p9_proto_tremove(struct p9_req *req, const virtio_9p_msg_t *tmsg); +status_t p9_proto_rremove(struct p9_req *req, virtio_9p_msg_t *rmsg); +status_t p9_proto_rlerror(struct p9_req *req, virtio_9p_msg_t *rmsg); +status_t p9_proto_tlcreate(struct p9_req *req, const virtio_9p_msg_t *tmsg); +status_t p9_proto_rlcreate(struct p9_req *req, virtio_9p_msg_t *rmsg); +status_t p9_proto_treaddir(struct p9_req *req, const virtio_9p_msg_t *tmsg); +status_t p9_proto_rreaddir(struct p9_req *req, virtio_9p_msg_t *rmsg); +status_t p9_proto_tmkdir(struct p9_req *req, const virtio_9p_msg_t *tmsg); +status_t p9_proto_rmkdir(struct p9_req *req, virtio_9p_msg_t *rmsg); diff --git a/dev/virtio/9p/rules.mk b/dev/virtio/9p/rules.mk new file mode 100644 index 0000000000..95981b0cde --- /dev/null +++ b/dev/virtio/9p/rules.mk @@ -0,0 +1,13 @@ +LOCAL_DIR := $(GET_LOCAL_DIR) + +MODULE := $(LOCAL_DIR) + +MODULE_SRCS += \ + $(LOCAL_DIR)/client.c \ + $(LOCAL_DIR)/virtio-9p.c \ + $(LOCAL_DIR)/protocol.c + +MODULE_DEPS += \ + dev/virtio + +include make/module.mk diff --git a/dev/virtio/9p/virtio-9p.c b/dev/virtio/9p/virtio-9p.c new file mode 100644 index 0000000000..33738026fc --- /dev/null +++ b/dev/virtio/9p/virtio-9p.c @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2023, Google Inc. All rights reserved. + * Author: codycswong@google.com (Cody Wong) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "protocol.h" + +#define LOCAL_TRACE 0 + +struct virtio_9p_config { + uint16_t tag_len; + uint8_t tag[]; +}; + +#define VIRTIO_9P_MOUNT_TAG (1<<0) + +static enum handler_return virtio_9p_irq_driver_callback( + struct virtio_device *dev, uint ring, const struct vring_used_elem *e); + +static struct list_node p9_devices = LIST_INITIAL_VALUE(p9_devices); + +static void dump_feature_bits(uint32_t feature) +{ + LTRACEF("virtio-9p host features (0x%x):", feature); + if (feature & VIRTIO_9P_MOUNT_TAG) LTRACEF(" MOUNT_TAG"); + LTRACEF("\n"); +} + +status_t virtio_9p_init(struct virtio_device *dev, uint32_t host_features) +{ + dump_feature_bits(host_features); + + /* allocate a new 9p device */ + struct virtio_9p_dev *p9dev = calloc(1, sizeof(struct virtio_9p_dev)); + if (!p9dev) + return ERR_NO_MEMORY; + + p9dev->dev = dev; + dev->priv = p9dev; + p9dev->lock = SPIN_LOCK_INITIAL_VALUE; + // Assuming there can be only one outstanding request. + p9dev->req.status = P9_REQ_S_UNKNOWN; + mutex_init(&p9dev->req_lock); + p9dev->msize = VIRTIO_9P_DEFAULT_MSIZE; + + // Add the 9p device to the device list + list_add_tail(&p9_devices, &p9dev->list); + + /* make sure the device is reset */ + virtio_reset_device(dev); + + p9dev->config = (struct virtio_9p_config *)dev->config_ptr; +#if LOCAL_TRACE + LTRACEF("tag_len: %u\n", p9dev->config->tag_len); + LTRACEF("tag: "); + for (int i = 0; i < p9dev->config->tag_len; ++i) { + printf("%c", p9dev->config->tag[i]); + } + printf("\n"); +#endif + + /* ack and set the driver status bit */ + virtio_status_acknowledge_driver(dev); + + virtio_alloc_ring(dev, VIRTIO_9P_RING_IDX, VIRTIO_9P_RING_SIZE); + + /* set our irq handler */ + dev->irq_driver_callback = &virtio_9p_irq_driver_callback; + + /* set DRIVER_OK */ + virtio_status_driver_ok(dev); + + // register a fake block device + static uint8_t found_index = 0; + char buf[16]; + snprintf(buf, sizeof(buf), "v9p%u", found_index++); + bio_initialize_bdev(&p9dev->bdev, buf, 1, 0, + 0, NULL, BIO_FLAGS_NONE); + + // override our block device hooks + p9dev->bdev.read_block = NULL; + p9dev->bdev.write_block = NULL; + + bio_register_device(&p9dev->bdev); + + return NO_ERROR; +} + +status_t virtio_9p_start(struct virtio_device *dev) +{ + struct virtio_9p_dev *p9dev = (struct virtio_9p_dev *)dev->priv; + status_t ret; + + // connect to the 9p server with 9P2000.L + virtio_9p_msg_t tver = { + .msg_type = P9_TVERSION, + .tag = P9_TAG_NOTAG, + .msg.tversion = {.msize = p9dev->msize, .version = "9P2000.L"} + }; + virtio_9p_msg_t rver = {}; + + if ((ret = virtio_9p_rpc(dev, &tver, &rver)) != NO_ERROR) + return ret; + + // assert the server support 9P2000.L version + ASSERT(strcmp(rver.msg.rversion.version, "9P2000.L") == 0); + p9dev->msize = rver.msg.rversion.msize; + + virtio_9p_msg_destroy(&rver); + + return NO_ERROR; +} + +static enum handler_return virtio_9p_irq_driver_callback( + struct virtio_device *dev, uint ring, const struct vring_used_elem *e) +{ + struct virtio_9p_dev *p9dev = (struct virtio_9p_dev *)dev->priv; + uint16_t id = e->id; + uint16_t id_next; + struct vring_desc *desc = virtio_desc_index_to_desc(dev, ring, id); + struct p9_req *req = &p9dev->req; + + LTRACEF("dev %p, ring %u, e %p, id %u, len %u\n", dev, ring, e, e->id, e->len); +#if LOCAL_TRACE + virtio_dump_desc(desc); +#endif + + ASSERT(req->status == P9_REQ_S_SENT); + ASSERT(desc); + ASSERT(desc->flags & VRING_DESC_F_NEXT); + + spin_lock(&p9dev->lock); + + // drop the T-message desc + id_next = desc->next; + desc = virtio_desc_index_to_desc(dev, VIRTIO_9P_RING_IDX, id_next); +#if LOCAL_TRACE + virtio_dump_desc(desc); +#endif + req->rc.size = e->len; + req->status = P9_REQ_S_RECEIVED; + + // free the desc + virtio_free_desc(dev, ring, id); + virtio_free_desc(dev, ring, id_next); + + spin_unlock(&p9dev->lock); + + /* wake up the rpc */ + event_signal(&req->io_event, false); + + return INT_RESCHEDULE; +} + +static struct virtio_9p_dev *bdev_to_virtio_9p_dev(bdev_t *bdev) +{ + return containerof(bdev, struct virtio_9p_dev, bdev); +} + +struct virtio_device *virtio_9p_bdev_to_virtio_device(bdev_t *bdev) +{ + return bdev_to_virtio_9p_dev(bdev)->dev; +} + +struct virtio_device *virtio_get_9p_device(uint index) +{ + struct virtio_9p_dev *p9dev; + uint count = 0; + + list_for_every_entry(&p9_devices, p9dev, struct virtio_9p_dev, list) { + if (count == index) + return p9dev->dev; + count++; + } + + return NULL; +} diff --git a/dev/virtio/virtio.c b/dev/virtio/virtio.c index 182520a2e6..e6ab620086 100644 --- a/dev/virtio/virtio.c +++ b/dev/virtio/virtio.c @@ -35,6 +35,9 @@ #if WITH_DEV_VIRTIO_GPU #include #endif +#if WITH_DEV_VIRTIO_9P +#include +#endif #define LOCAL_TRACE 0 @@ -183,6 +186,25 @@ int virtio_mmio_detect(void *ptr, uint count, const uint irqs[], size_t stride) } } #endif // WITH_DEV_VIRTIO_NET +#if WITH_DEV_VIRTIO_9P + if (mmio->device_id == 9) { // 9p device + LTRACEF("found 9p device\n"); + + dev->mmio_config = mmio; + dev->config_ptr = (void *)mmio->config; + + status_t err = virtio_9p_init(dev, mmio->host_features); + if (err >= 0) { + // good device + dev->valid = true; + + if (dev->irq_driver_callback) + unmask_interrupt(dev->irq); + + virtio_9p_start(dev); + } + } +#endif // WITH_DEV_VIRTIO_9P #if WITH_DEV_VIRTIO_GPU if (mmio->device_id == 0x10) { // virtio-gpu LTRACEF("found gpu device\n"); diff --git a/platform/qemu-virt-arm/rules.mk b/platform/qemu-virt-arm/rules.mk index 79721db2c5..eb00bda1d9 100644 --- a/platform/qemu-virt-arm/rules.mk +++ b/platform/qemu-virt-arm/rules.mk @@ -33,6 +33,7 @@ MODULE_DEPS += \ dev/virtio/block \ dev/virtio/gpu \ dev/virtio/net \ + dev/virtio/9p \ lib/cbuf \ lib/fdtwalk \ diff --git a/scripts/do-qemuarm b/scripts/do-qemuarm index d5ea9a1dbb..70b7534352 100755 --- a/scripts/do-qemuarm +++ b/scripts/do-qemuarm @@ -17,6 +17,7 @@ function HELP { echo "-n : a virtio network device" echo "-t : a virtio tap network device" echo "-g : a virtio display" + echo "-f : a virtio 9p device with a host shared directory" echo echo "-h for help" echo "all arguments after -- are passed to qemu directly" @@ -33,16 +34,19 @@ DO_CORTEX_M3=0 DO_DISPLAY=0 DO_CMPCTMALLOC=0 DO_MINIHEAP=0 +DO_V9P=0 +DO_V9P_DIR="" SMP=1 MEMSIZE=512 SUDO="" PROJECT="" -while getopts cd:ghm:Mnt36vp:s: FLAG; do +while getopts cd:ghm:Mnt36vp:s:f: FLAG; do case $FLAG in c) DO_CMPCTMALLOC=1;; d) DO_DISK=1; DISK_IMAGE=$OPTARG;; g) DO_DISPLAY=1;; + f) DO_V9P=1; DO_V9P_DIR=$OPTARG;; M) DO_MINIHEAP=1;; n) DO_NET=1;; t) DO_NET_TAP=1;; @@ -121,6 +125,11 @@ else ARGS+=" -nographic" fi +if (( $DO_V9P )); then + ARGS+=" -fsdev local,path=$DO_V9P_DIR,security_model=mapped,id=v9p0" + ARGS+=" -device virtio-9p-device,fsdev=v9p0,mount_tag=V9P0" +fi + MAKE_VARS="" if (( $DO_CMPCTMALLOC )); then