[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 <codycswong@google.com>
This commit is contained in:
Cody Wong
2023-12-26 12:41:33 +08:00
committed by Travis Geiselbrecht
parent 3288b15a39
commit 2b02c8a046
7 changed files with 1770 additions and 0 deletions

347
dev/virtio/9p/client.c Normal file
View File

@@ -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 <dev/virtio.h>
#include <dev/virtio/9p.h>
#include <kernel/event.h>
#include <kernel/vm.h>
#include <lk/debug.h>
#include <lk/err.h>
#include <lk/trace.h>
#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;
}
}

View File

@@ -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 <dev/virtio.h>
#include <lib/bio.h>
#include <lk/compiler.h>
#include <kernel/spinlock.h>
#include <sys/types.h>
#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);

606
dev/virtio/9p/protocol.c Normal file
View File

@@ -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 <dev/virtio/9p.h>
#include <inttypes.h>
#include <lk/err.h>
#include <lk/trace.h>
#include <stdlib.h>
#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;
}

116
dev/virtio/9p/protocol.h Normal file
View File

@@ -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 <dev/virtio/9p.h>
#include <kernel/event.h>
#include <kernel/mutex.h>
#include <lk/list.h>
#include <sys/types.h>
#include <string.h>
#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);

13
dev/virtio/9p/rules.mk Normal file
View File

@@ -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

205
dev/virtio/9p/virtio-9p.c Normal file
View File

@@ -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 <assert.h>
#include <dev/virtio/9p.h>
#include <kernel/event.h>
#include <lk/debug.h>
#include <lk/err.h>
#include <lk/list.h>
#include <lk/trace.h>
#include <stdlib.h>
#include <string.h>
#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;
}

View File

@@ -35,6 +35,9 @@
#if WITH_DEV_VIRTIO_GPU
#include <dev/virtio/gpu.h>
#endif
#if WITH_DEV_VIRTIO_9P
#include <dev/virtio/9p.h>
#endif
#define LOCAL_TRACE 0
@@ -185,6 +188,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");