From 8de633bc1c0dd2fcebcf84bab23ffabe07c56960 Mon Sep 17 00:00:00 2001 From: Cody Wong Date: Tue, 26 Dec 2023 12:41:33 +0800 Subject: [PATCH] [virtio][v9p] Add the VirtIO 9p device driver This commit adds the VirtIO 9p device driver based on the VirtIO driver stack in LK, `dev/virtio`. The driver supports a subset of 9P2000.L protocol (https://github.com/chaos/diod/blob/master/protocol.md), which is able to perform basic file operations (fread, fwrite, dirread, etc.). The primary interface for sending and receiving the 9p messages is `virtio_9p_rpc`, which is handy and scalable. The driver is limited to communicate to the host with only one outstanding 9p message per device due to the simplified driver design. Basically that is enough for embedded environments when there is no massive file IO. Signed-off-by: Cody Wong --- dev/virtio/9p/client.c | 347 +++++++++++++++ dev/virtio/9p/include/dev/virtio/9p.h | 461 ++++++++++++++++++++ dev/virtio/9p/protocol.c | 606 ++++++++++++++++++++++++++ dev/virtio/9p/protocol.h | 116 +++++ dev/virtio/9p/rules.mk | 20 + dev/virtio/9p/virtio-9p.c | 205 +++++++++ dev/virtio/virtio.c | 22 + 7 files changed, 1777 insertions(+) create mode 100644 dev/virtio/9p/client.c create mode 100644 dev/virtio/9p/include/dev/virtio/9p.h create mode 100644 dev/virtio/9p/protocol.c create mode 100644 dev/virtio/9p/protocol.h create mode 100644 dev/virtio/9p/rules.mk create mode 100644 dev/virtio/9p/virtio-9p.c diff --git a/dev/virtio/9p/client.c b/dev/virtio/9p/client.c new file mode 100644 index 000000000..1fb3c8bed --- /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 000000000..829d0b3e6 --- /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 000000000..b00e6bff5 --- /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 000000000..0a52a750e --- /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 000000000..defa8393b --- /dev/null +++ b/dev/virtio/9p/rules.mk @@ -0,0 +1,20 @@ +LOCAL_DIR := $(GET_LOCAL_DIR) + +MODULE := $(LOCAL_DIR) + +MODULE_CFLAGS += -Wall -Werror + +ifneq ($(V9P_HOST_DIR),) +GLOBAL_DEFINES += \ + V9P_HOST_DIR=\"$(V9P_HOST_DIR)\" +endif + +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 000000000..33738026f --- /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 182520a2e..e6ab62008 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");