Page MenuHomeFreeBSD

D45993.id141117.diff
No OneTemporary

D45993.id141117.diff

diff --git a/sbin/Makefile b/sbin/Makefile
--- a/sbin/Makefile
+++ b/sbin/Makefile
@@ -44,6 +44,7 @@
mount_msdosfs \
mount_nfs \
mount_nullfs \
+ mount_qemufwcfg \
mount_udf \
mount_unionfs \
newfs \
diff --git a/sbin/mount_qemufwcfg/Makefile b/sbin/mount_qemufwcfg/Makefile
new file mode 100644
--- /dev/null
+++ b/sbin/mount_qemufwcfg/Makefile
@@ -0,0 +1,15 @@
+.include <bsd.sysdir.mk>
+
+PROG_CXX=mount_qemufwcfg
+SRCS= mount_qemufwcfg.cc
+MAN= mount_qemufwcfg.8
+
+WARNS?= 5
+
+CXXSTD= c++20
+
+CXXFLAGS+=-I${SYSDIR}/fs/fuse -I${SYSDIR}
+
+NO_SHARED?=NO
+
+.include <bsd.prog.mk>
diff --git a/sbin/mount_qemufwcfg/mount_qemufwcfg.8 b/sbin/mount_qemufwcfg/mount_qemufwcfg.8
new file mode 100644
--- /dev/null
+++ b/sbin/mount_qemufwcfg/mount_qemufwcfg.8
@@ -0,0 +1,96 @@
+.\" $NetBSD: mount_qemufwcfg.8,v 1.3 2020/04/29 09:54:43 gson Exp $
+.\"
+.\" Copyright (c) 2017 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd April 29, 2020
+.Dt MOUNT_QEMUFWCFG 8
+.Os
+.Sh NAME
+.Nm mount_qemufwcfg
+.Nd provide QEMU fw_cfg data as a file system
+.Sh SYNOPSIS
+.Nm
+.Op Fl F Ar path
+.Op Fl g Ar gid
+.Op Fl M Ar dir-mode
+.Op Fl m Ar file-mode
+.Op Fl u Ar uid
+.Op Ar fuse-options
+.Ar node
+.Sh DESCRIPTION
+The
+.Nm
+command provides the QEMU fw_cfg configuration files in a file system
+tree at point
+.Ar node .
+The directory specified by
+.Ar node
+is converted to an absolute path before use.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl F Ar path
+Use
+.Ar path
+instead of
+.Pa /dev/qemufwcfg
+for the QEMU device.
+.It Fl g Ar gid
+Use
+.Ar gid
+as group for files in the file system instead of the active group id.
+.It Fl M Ar dir-mode
+Use
+.Ar dir-mode
+as permissions for directories instead of the default
+.Ar 0555 .
+.It Fl m Ar file-mode
+Use
+.Ar file-mode
+as permissions for files instead of the default
+.Ar 0444 .
+.It Fl u Ar uid
+Use
+.Ar uid
+as user for files in the file system instead of the active user id.
+.El
+.Sh SEE ALSO
+.Xr qemufwcfg 4
+.Sh HISTORY
+A
+.Nm
+command first appeared in
+.Nx 9.0 .
+The
+.Fx
+version is a reimplementation to avoid the libfuse dependency and add
+Capsicum support.
+.Sh AUTHORS
+The utility was written by
+.An David Chisnall
+This man page and the
+.Nx
+version were written by
+.An Jared McNeill Aq Mt jmcneill@invisible.ca
diff --git a/sbin/mount_qemufwcfg/mount_qemufwcfg.cc b/sbin/mount_qemufwcfg/mount_qemufwcfg.cc
new file mode 100644
--- /dev/null
+++ b/sbin/mount_qemufwcfg/mount_qemufwcfg.cc
@@ -0,0 +1,742 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2024 David Chisnall <theraven@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef WITHOUT_CAPSICUM
+#if __has_include(<sys/capsicum.h>)
+#include <sys/capsicum.h>
+#else
+#define WITHOUT_CAPSICUM
+#endif
+#endif
+
+#include <sys/stat.h>
+
+#include <dev/ic/qemufwcfgio.h>
+
+#include <netinet/in.h>
+
+#include <fcntl.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "tinyfuse.hh"
+#include <cassert>
+#include <iostream>
+#include <limits>
+#include <map>
+#include <memory>
+#include <ranges>
+#include <stdexcept>
+#include <string>
+#include <unordered_map>
+#include <variant>
+
+namespace {
+
+/**
+ * Enable debugging messages if we are compiling a debug build.
+ */
+constexpr bool Debug = false;
+
+/**
+ * Enable caching of files. This adds a very small performance improvement
+ * (typically under 10%)in exchange for some memory overhead. This defaults to
+ * off because performance is very rarely a requirement for this. We can add a
+ * configuration option to enable it if someone has a use case.
+ */
+constexpr bool Cache = false;
+
+/**
+ * Class implementing a FUSE filesystem for the QEMU FW CFG device.
+ *
+ * The underlying device contains a set of blobs indexed by a 16-bit
+ * identifier. One of these is a catalogue, providing name to index mappings.
+ * Those names may contain slashes and so can be interpreted as paths. This
+ * filesystem builds a virtual directory structure from the names and exposes
+ * them as a real filesystem.
+ */
+class QemuFWCfgFuseFS : public FuseFS<QemuFWCfgFuseFS, Debug> {
+ /**
+ * File structure. Contains the information from the device: the size
+ * and the selector used to access this 'file'.
+ */
+ struct File {
+ /// The file size.
+ uint32_t size = 0;
+ /// The selector for this file.
+ uint16_t selector = 0;
+ };
+
+ /**
+ * QEMU Firmware Config 'file'. This is the catalog entry returned from
+ * the device to describe the names of the other entries.
+ */
+ struct FWCfgFile {
+ /// The number of bytes of the 'file' that this describes.
+ uint32_t size; /* size of referenced fw_cfg item, big-endian */
+ /// The selector used to access this file.
+ uint16_t selector; /* selector key of fw_cfg item, big-endian */
+ /// Padding
+ uint16_t reserved;
+ /// Full file path, null-terminated string.
+ char name[56]; /* fw_cfg item name, NUL-terminated ascii */
+ };
+
+ struct Directory;
+
+ /**
+ * Helper for (shared) pointers to directories.
+ */
+ using DirectoryPointer = std::shared_ptr<Directory>;
+
+ /**
+ * Objects in the 'filesystem' are either other directories or files.
+ */
+ using FilesystemObject = std::variant<File, DirectoryPointer>;
+
+ /**
+ * Directory. Contains a map of names to children, which may be files
+ * or other directories.
+ */
+ struct Directory {
+ /**
+ * The first inode to allocate to a directory.
+ */
+ static constexpr uint32_t FirstDirectoryInode =
+ std::numeric_limits<decltype(File::selector)>::max() + 1;
+
+ /**
+ * Next inode number to assign to directories. Directory inodes
+ * are allocated after selectors.
+ */
+ inline static uint32_t nextDirectoryInode = FirstDirectoryInode;
+
+ /**
+ * Ordered map from file names to children.
+ */
+ std::map<std::string, FilesystemObject> children;
+
+ /**
+ * Cached version of the directory entries.
+ */
+ VariableSizeResponse direntCache;
+
+ /**
+ * Inode for this directory.
+ */
+ const uint32_t inode;
+
+ /**
+ * Default constructor, allocates a directory with the next
+ * available inode.
+ */
+ Directory()
+ : Directory(nextDirectoryInode++)
+ {
+ }
+
+ /**
+ * Construct a directory with the specified inode.
+ */
+ Directory(uint32_t inode)
+ : inode(inode)
+ {
+ }
+ };
+
+ /**
+ * Look up the inode for an object in the filesystem. This is either
+ * the selector for 'files' or a number outside the valid selector range
+ * for directories.
+ */
+ uint32_t inode_for_filesystem_object(FilesystemObject filesystemObject)
+ {
+ uint32_t ret = 0;
+ std::visit(
+ [&ret](auto &&object) {
+ if constexpr (std::is_same_v<File,
+ std::remove_cvref_t<
+ decltype(object)>>) {
+ ret = object.selector;
+ } else if constexpr (std::is_same_v<
+ DirectoryPointer,
+ std::remove_cvref_t<
+ decltype(object)>>) {
+ ret = object->inode;
+ } else {
+ }
+ },
+ filesystemObject);
+ return ret;
+ }
+
+ /**
+ * Add a subdirectory to the current directory. If the directory
+ * already exists, the existing one is returned, otherwise a new one is
+ * allocated and returned.
+ */
+ DirectoryPointer add_subdirectory(Directory &parent,
+ const std::string &name)
+ {
+ auto it = parent.children.find(name);
+ if (it != parent.children.end()) {
+ if (std::holds_alternative<DirectoryPointer>(
+ it->second)) {
+ return std::get<DirectoryPointer>(it->second);
+ }
+ throw std::invalid_argument(
+ "Directory is a regular file");
+ }
+ auto newDirectory = std::make_shared<Directory>();
+ parent.children[name] = newDirectory;
+ inodes[newDirectory->inode] = newDirectory;
+ return newDirectory;
+ }
+
+ /**
+ * Add a file in the specified directory.
+ */
+ void add_file(Directory &parent, const std::string &name, uint32_t size,
+ uint16_t selector)
+ {
+ parent.children[name] = File { size, selector };
+ inodes[selector] = parent.children[name];
+ }
+
+ /**
+ * Returns true if the inode is a directory inode, false if not. This
+ * does not require any file or directory to actually exist for this
+ * inode number.
+ */
+ bool is_directory(uint64_t inode)
+ {
+ return (inode == FUSE_ROOT_ID) ||
+ inode >= Directory::FirstDirectoryInode;
+ }
+
+ /**
+ * Root directory.
+ */
+ DirectoryPointer root = std::make_shared<Directory>(FUSE_ROOT_ID);
+
+ /**
+ * Map from inode number to the object that they refer to.
+ */
+ std::unordered_map<uint64_t, FilesystemObject> inodes;
+
+ /**
+ * The total number of files in this filesystem.
+ */
+ uint16_t numberOfFiles = 0;
+
+ /**
+ * The total number of bytes in this filesystem.
+ */
+ uint32_t totalSize = 0;
+
+ /**
+ * Buffers that cache the contents of files. We currently never
+ * invalidate these because the interface is not used to deliver very
+ * large files. The filesystem can be unmounted and remounted to clear
+ * the cache.
+ *
+ * If this is a problem, it's easy to add some cache invalidation later.
+ */
+ std::unordered_map<uint16_t, Buffer> fileCaches;
+
+ /**
+ * Time (in seconds) when the filesystem was mounted. All files are
+ * treated as being created at that time.
+ */
+ const uint32_t timeS;
+
+ /**
+ * File descriptor for the QEMU FWCFG device.
+ */
+ int qemuFWCfgFD;
+
+ /**
+ * The GID used for files in this filesystem
+ */
+ gid_t defaultGid;
+
+ /**
+ * The UID used for files in this filesystem
+ */
+ uid_t defaultUid;
+
+ /**
+ * The mode for directories in this filesystem.
+ */
+ mode_t defaultDirectoryMode;
+
+ /**
+ * The mode for files in this filesystem.
+ */
+ mode_t defaultFileMode;
+
+ public:
+ /**
+ * Constructor. Reads the catalog from the device and prepares the
+ * filesystem structure.
+ */
+ QemuFWCfgFuseFS(const char *devicePath, gid_t defaultGid,
+ uid_t defaultUid, mode_t defaultDirectoryMode,
+ mode_t defaultFileMode)
+ : timeS(time(nullptr))
+ , defaultGid(defaultGid)
+ , defaultUid(defaultUid)
+ , defaultDirectoryMode(defaultDirectoryMode)
+ , defaultFileMode(defaultFileMode)
+ {
+ // Open the qemufwcfg device. This can fail if this filesystem
+ // is already mounted: it is designed for a single userspace
+ // consumer.
+ qemuFWCfgFD = open(devicePath, O_RDWR);
+ if (qemuFWCfgFD < 0) {
+ throw std::system_error(errno, std::generic_category(),
+ "Failed to open qemufwcfg device");
+ }
+ // Set the selector to the index for the well-known blob
+ // containing the catalogue.
+ uint16_t selector = FW_CFG_FILE_DIR;
+ ioctl(qemuFWCfgFD, FWCFGIO_SET_INDEX, &selector);
+ // Read the number of entries (big endian).
+ uint32_t count;
+ if (int ret = read(qemuFWCfgFD, &count, sizeof(count));
+ ret != sizeof(count)) {
+ throw std::system_error(errno, std::generic_category(),
+ "Failed to read number of entries in qemufwcfg device");
+ }
+ debug_message("Found {} firmware entries", count);
+ count = ntohl(count);
+ numberOfFiles = count;
+ // Read each entry and build the required directory structure.
+ for (uint32_t i = 0; i < count; i++) {
+ FWCfgFile file;
+ read(qemuFWCfgFD, &file, sizeof(file));
+ debug_message("File name: {}, size: {}, selector: {}",
+ file.name, ntohl(file.size), ntohs(file.selector));
+ totalSize += ntohl(file.size);
+ std::string_view path { file.name };
+ size_t nextSlash;
+ auto dir = root;
+ // If this name contains any slashes, construct a
+ // directory hierarchy leading up to the directory
+ // containing the file.
+ while ((nextSlash = path.find('/')) !=
+ std::string_view::npos) {
+ std::string pathComponent { path.substr(0,
+ nextSlash) };
+ dir = add_subdirectory(*dir, pathComponent);
+ path = path.substr(nextSlash + 1);
+ }
+ // Insert the file into the directory.
+ add_file(*dir, std::string { path }, ntohl(file.size),
+ ntohs(file.selector));
+ }
+ // Insert the root directory into the inodes map.
+ inodes[FUSE_ROOT_ID] = root;
+ }
+
+ /**
+ * Implement stat functionality.
+ */
+ ErrorOr<fuse_attr_out> fuse_getattr(const fuse_in_header &header,
+ const fuse_getattr_in &attrIn)
+ {
+ uint64_t inode = header.nodeid;
+ debug_message("GetAttr flags: {}, inode: {}",
+ attrIn.getattr_flags, inode);
+ bool isDirectory = is_directory(inode);
+ // If this is a directory, the size is zero, otherwise look up
+ // the size.
+ uint64_t size = 0;
+ if (!isDirectory) {
+ size = std::get<File>(inodes[inode]).size;
+ }
+ fuse_attr_out out;
+ memset(&out, 0, sizeof(out));
+ // Read-only filesystem, make the cache timeout the distant
+ // future.
+ out.attr_valid =
+ std::numeric_limits<decltype(out.attr_valid)>::max() / 2;
+ out.attr_valid_nsec = 0; // 0x10000;
+ out.dummy = 0;
+ // Attributes
+ set_attrs(out.attr, inode, size, isDirectory);
+ return out;
+ }
+
+ /**
+ * Read from a file.
+ *
+ * The underlying device does not support seeking and so this will read
+ * the entire file and cache it if `UseCache` is true, otherwise it will
+ * read all data from the device up to the requested point, discard it,
+ * and then read the requested part.
+ */
+ template <bool UseCache>
+ ErrorOr<std::conditional_t<UseCache,
+ std::ranges::subrange<Buffer::iterator>, Buffer>>
+ fuse_read_helper(const fuse_in_header &header,
+ const fuse_read_in &readIn)
+ {
+ debug_message(
+ "read {{ fh: {}, offset: {}, size: {}, read_flags: {}, lock_owner: {}, flags: {} }}",
+ readIn.fh, readIn.offset, readIn.size, readIn.read_flags,
+ readIn.lock_owner, readIn.flags);
+ auto &item = inodes[header.nodeid];
+ if (!std::holds_alternative<File>(item)) {
+ return EINVAL;
+ }
+ auto file = std::get<File>(item);
+ Buffer out;
+ out.resize(readIn.size);
+ int ret = ioctl(qemuFWCfgFD, FWCFGIO_SET_INDEX, &file.selector);
+ if (ret != 0) {
+ throw std::system_error(errno, std::generic_category(),
+ "Failed to switch selector in qemufwcfg device");
+ }
+ auto readToBuffer = [&](size_t size) {
+ Buffer buffer;
+ buffer.resize(size);
+ size_t readData = 0;
+ while (readData < size) {
+ debug_message("read({}, {}, {})", qemuFWCfgFD,
+ buffer.data() + readData,
+ buffer.size() - readData);
+ ssize_t result = read(qemuFWCfgFD,
+ buffer.data() + readData,
+ buffer.size() - readData);
+ debug_message("Read returned {}", result);
+ // FIXME!!!!!
+ if (result <= 0) {
+ if (errno == EAGAIN) {
+ continue;
+ }
+ throw std::system_error(errno,
+ std::generic_category(),
+ "Failed to read from qemufwcfg device");
+ }
+ readData += result;
+ }
+ return buffer;
+ };
+ if constexpr (UseCache) {
+ auto &cache = fileCaches[file.selector];
+ if (cache.size() < file.size) {
+ debug_message(
+ "Reading {} bytes to populate cache",
+ file.size);
+ cache = (readToBuffer(file.size));
+ }
+ debug_message("Returning subrange from cache");
+ return std::ranges::subrange(cache.begin() +
+ readIn.offset,
+ cache.begin() + readIn.offset + readIn.size);
+
+ } else {
+ if (readIn.offset > 0) {
+ debug_message(
+ "Reading {} bytes to get to offset",
+ readIn.offset);
+ readToBuffer(readIn.offset);
+ }
+ debug_message("Reading {} bytes to return",
+ readIn.size);
+ return readToBuffer(readIn.size);
+ }
+ }
+
+ /**
+ * Interface for fuse_read called from the superclass. This cannot be
+ * the templated version because then `dispatch` is unable to infer the
+ * implicit template argument.
+ */
+ auto fuse_read(const fuse_in_header &header, const fuse_read_in &readIn)
+ {
+ return fuse_read_helper<Cache>(header, readIn);
+ }
+
+ /**
+ * Read one or more directory entries.
+ */
+ ErrorOr<std::ranges::subrange<Buffer::iterator>>
+ fuse_readdir(const fuse_in_header &header, const fuse_read_in &readIn)
+ {
+ debug_message(
+ "readdir {{ fh: {}, offset: {}, size: {}, read_flags: {}, lock_owner: {}, flags: {} }}",
+ readIn.fh, readIn.offset, readIn.size, readIn.read_flags,
+ readIn.lock_owner, readIn.flags);
+ auto &item = inodes[header.nodeid];
+ if (!std::holds_alternative<DirectoryPointer>(item)) {
+ return EINVAL;
+ }
+ auto &directory = *std::get<DirectoryPointer>(item);
+ VariableSizeResponse &dirents = directory.direntCache;
+ if (dirents.empty()) {
+ auto roundUp8 = [](size_t size) {
+ return ((size + 8 - 1) / 8) * 8;
+ };
+ // For some reason (to be debugged) the kernel doesn't
+ // like these if you give them the correct inode values,
+ // but is happy with -1 as a 32-bit integer.
+ //
+ // Normal dirents use 0 as the indicator of the position
+ // of the next one, but FUSE uses -1. This, like
+ // everything else about FUSE, is undocumented.
+ auto addDirent = [&](std::string_view name,
+ bool isLast = false,
+ uint32_t inode = 0xffff'ffff) {
+ auto initialSize = dirents.size();
+ auto next = roundUp8(
+ sizeof(fuse_dirent) + name.size());
+ dirents << fuse_dirent { inode,
+ isLast ? -1 : next,
+ static_cast<uint32_t>(name.size()), 0 }
+ << name;
+ dirents.pad_to_alignment(8);
+ auto length = dirents.size() - initialSize;
+ debug_message(
+ "Added dirent at offset {}, next: {}",
+ initialSize, next);
+ assert(length == next);
+ };
+ addDirent(".");
+ addDirent("..");
+ if (directory.children.size() > 0) {
+ for (auto i : std::ranges::subrange(
+ directory.children.begin(),
+ --directory.children.end())) {
+ addDirent(i.first);
+ }
+ }
+ // Add the last entry.
+ addDirent((--directory.children.end())->first, true);
+ }
+ debug_message("Dirents size: {}, number of entries: {}",
+ dirents.size(), directory.children.size());
+ if (readIn.offset >= dirents.size()) {
+ debug_message("Writing no dirent for >0 offset {}",
+ readIn.offset);
+ return 0;
+ }
+ size_t size = std::min<size_t>(readIn.size,
+ dirents.size() - readIn.offset);
+ return std::ranges::subrange(dirents.begin() + readIn.offset,
+ dirents.begin() + size);
+ }
+
+ /**
+ * Look up a path component in a directory.
+ */
+ ErrorOr<fuse_entry_out> fuse_lookup(const fuse_in_header &header,
+ const char *path)
+ {
+ // Find the directory from the inode.
+ auto &containingDirectory = inodes[header.nodeid];
+ if (!std::holds_alternative<DirectoryPointer>(
+ containingDirectory)) {
+ return EINVAL;
+ }
+
+ auto &directory = *std::get<DirectoryPointer>(
+ containingDirectory);
+
+ // Find the entry in the directory.
+ std::string filename { path };
+ auto &item = directory.children[filename];
+ debug_message("Look up: {}", path);
+
+ fuse_entry_out out;
+ memset(&out, 0, sizeof(out));
+ out.nodeid = inode_for_filesystem_object(item);
+ out.generation = 0;
+ // Maximum possible timeout. We are an immutable filesystem.
+ out.entry_valid =
+ std::numeric_limits<decltype(out.entry_valid)>::max();
+ out.attr_valid =
+ std::numeric_limits<decltype(out.entry_valid)>::max();
+ out.entry_valid_nsec = 0;
+ out.attr_valid_nsec = 0;
+ uint64_t size = 0;
+ bool isDirectory = true;
+ if (std::holds_alternative<File>(item)) {
+ size = std::get<File>(item).size;
+ debug_message("File size is {}", size);
+ isDirectory = false;
+ } else {
+ debug_message("{} is a directory", path);
+ }
+ set_attrs(out.attr, inode_for_filesystem_object(item), size,
+ isDirectory);
+ return out;
+ }
+
+ /**
+ * Set the fuse attributes for a file or directory given an inode number
+ * and size.
+ */
+ void set_attrs(fuse_attr &attr, uint64_t inode, uint64_t size,
+ bool isDirectory)
+ {
+ static constexpr uint64_t BlockSize = 512;
+ // Inode number
+ attr.ino = inode;
+ attr.size = size;
+ attr.blocks = size / BlockSize;
+ // Read-only filesystem, everything was created at the time when
+ // we mounted the filesystem.
+ attr.atime = attr.mtime = attr.ctime = timeS;
+ attr.atimensec = attr.mtimensec = attr.ctimensec = 0;
+ // Read-only
+ attr.mode = isDirectory ? (defaultDirectoryMode | S_IFDIR) :
+ (defaultFileMode | S_IFREG);
+ // No links on this filesystem, give everything a link count of
+ // one.
+ attr.nlink = 1;
+ attr.uid = defaultUid;
+ attr.gid = defaultGid;
+ attr.rdev = 0;
+ attr.blksize = BlockSize;
+ }
+
+ /**
+ * Statfs. Return the number of files in the filesystem.
+ */
+ ErrorOr<fuse_statfs_out> fuse_statfs(const fuse_in_header &header)
+ {
+ fuse_statfs_out out = std::get<fuse_statfs_out>(
+ FuseFS::fuse_statfs(header));
+ out.st.files = numberOfFiles;
+ // Use the default block size
+ out.st.frsize = 512;
+ out.st.blocks = totalSize / 512;
+ out.st.namelen = sizeof(FWCfgFile::name);
+ return out;
+ }
+};
+
+}
+
+int
+main(int argc, char **argv)
+{
+ // Is this a direct invocation, or via mount_fusefs?
+ bool directInvocation = (getenv("FUSE_DEV_FD") == nullptr);
+
+ // Default configuration options.
+ const char *devicePath = "/dev/qemufwcfg";
+ gid_t defaultGid = getgid();
+ uid_t defaultUid = getuid();
+ mode_t defaultDirectoryMode = S_IRUSR | S_IRGRP | S_IROTH | S_IXGRP |
+ S_IXOTH;
+ mode_t defaultFileMode = S_IRUSR | S_IRGRP | S_IROTH;
+
+ // Parse command-line flags.
+ const char *argv0 = argv[0];
+ int ch;
+ // Don't report illegal options, they are ones that we should forward to
+ // mount_fusefs
+ opterr = !directInvocation;
+ while ((ch = getopt(argc, argv, "F:g:M:m:u:h")) != -1) {
+ // Unknown options are assumed to be for mount_fusefs
+ if (directInvocation && (ch == '?')) {
+ optind--;
+ break;
+ }
+ switch (ch) {
+ case 'F':
+ devicePath = optarg;
+ break;
+ case 'm':
+ defaultFileMode = std::stoi(optarg, nullptr, 8) &
+ ACCESSPERMS;
+ break;
+ case 'M':
+ defaultDirectoryMode = std::stoi(optarg, nullptr, 8) &
+ ACCESSPERMS;
+ break;
+ case 'g':
+ defaultGid = std::stoi(optarg);
+ break;
+ case 'u':
+ defaultUid = std::stoi(optarg);
+ break;
+ case 'h':
+ default:
+ std::cerr
+ << "Usage: " << argv[0]
+ << " [-F path] [-g gid] [-M dir-mode] [-m file-mode] [-u uid] [fuse-options] node"
+ << std::endl;
+ return EXIT_SUCCESS;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ // If we are not invoked by mount_fusefs, exec mount_fusefs.
+ if (getenv("FUSE_DEV_FD") == nullptr) {
+ const char *mount_fusefs = "/sbin/mount_fusefs";
+ std::string daemonArgs = std::format(
+ "-F {} -g {} -M {} -m {} -u {}", devicePath, defaultGid,
+ defaultDirectoryMode, defaultUid, defaultUid);
+ std::vector<const char *> args;
+ args.push_back(mount_fusefs);
+ args.push_back("auto");
+ args.push_back("-O");
+ args.push_back(daemonArgs.c_str());
+ args.push_back("-D");
+ args.push_back(argv0);
+ for (int i = 0; i < argc; i++) {
+ args.push_back(argv[i]);
+ }
+ args.push_back(nullptr);
+ execv(mount_fusefs, const_cast<char *const *>(args.data()));
+ return EXIT_FAILURE;
+ }
+
+ QemuFWCfgFuseFS fs(devicePath, defaultGid, defaultUid,
+ defaultDirectoryMode, defaultFileMode);
+#ifndef WITHOUT_CAPSICUM
+ // Close standard in and out, restrict the rights on standard error to
+ // output (no ioctls, no read, and so on).
+ close(STDIN_FILENO);
+ close(STDOUT_FILENO);
+ cap_rights_t setrights;
+ cap_rights_init(&setrights, CAP_WRITE);
+ cap_rights_limit(STDERR_FILENO, &setrights);
+ cap_enter();
+#endif
+ try {
+ fs.run();
+ } catch (std::exception &e) {
+ std::cerr << "QEMU Firmware Filesystem failed: " << e.what()
+ << std::endl;
+ }
+}
diff --git a/sbin/mount_qemufwcfg/tinyfuse.hh b/sbin/mount_qemufwcfg/tinyfuse.hh
new file mode 100644
--- /dev/null
+++ b/sbin/mount_qemufwcfg/tinyfuse.hh
@@ -0,0 +1,704 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2024 David Chisnall <theraven@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#pragma once
+#include <sys/uio.h>
+
+#include <fuse_kernel.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <concepts>
+#include <format>
+#include <iostream>
+#include <stdexcept>
+#include <string>
+#include <system_error>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+/**
+ * Concept to differentiate between values that are provided as variable-sized
+ * objects.
+ */
+template <typename U>
+concept IsVariableSizedFuseStructure =
+ requires(U a) {
+ {
+ a.data()
+ } -> std::convertible_to<const void *>;
+ {
+ a.size()
+ } -> std::convertible_to<std::size_t>;
+ };
+
+namespace {
+
+/**
+ * CRTP class for a fuse filesystems.
+ *
+ * This allows tiny implementations of FUSE filesystems.
+ *
+ * It is intended for simplicity and not performance. If you care about
+ * performance, libfuse provides a rich set of interfaces for writing
+ * multithreaded FUSE filesystems.
+ *
+ * This interface currently supports read-only filesystems. It may be extended
+ * in the future for read-write ones.
+ *
+ * The `T` template parameter is the class that inherits from this. The
+ * `Debug` parameter controls whether (very!) verbose logging is enabled.
+ *
+ * Users subclass this class and implement methods for handling each FUSE
+ * request. NOTE: The return types of the overridden methods do *NOT* have to
+ * match the ones in this class. It is perfectly fine to return non-owning
+ * wrappers, for example, in places where an owning object is returned in the
+ * default case.
+ */
+template <typename T, bool Debug = false> class FuseFS {
+ protected:
+ /**
+ * Helper type for buffers of bytes.
+ */
+ using Buffer = std::vector<char>;
+
+ /**
+ * Error wrapper. Most FUSE commands return either a value appended to
+ * the response, or an error code in the response header.
+ */
+ template <typename U> using ErrorOr = std::variant<int, U>;
+
+ /**
+ * Log a debug message to standard output if compiled in debug mode,
+ * otherwise do nothing.
+ */
+ template <typename... Args>
+ void debug_message(std::string_view formatString, Args &&...args)
+ {
+ if constexpr (Debug) {
+ std::cerr << std::vformat(formatString,
+ std::make_format_args(args...))
+ << std::endl;
+ }
+ }
+
+ private:
+ /**
+ * The file descriptor for /dev/fuse
+ */
+ int fd = -1;
+
+ /**
+ * The maximum size of a response.
+ */
+ static constexpr size_t MaxReadSize = 128 * 1024;
+
+ /**
+ * The maximum size of a write (the largest size of a message body from
+ * the kernel to the daemon).
+ */
+ static constexpr size_t MaxWriteSize = 4 * 1024;
+
+ /**
+ * Read a single message from the FUSE device and return it as a header
+ * and a buffer containing the variable-sized portion.
+ */
+ std::pair<fuse_in_header, std::vector<char>> read_message()
+ {
+ fuse_in_header header;
+ Buffer message;
+ message.resize(MaxWriteSize);
+ // The fuse protocol is horrible. Each message must be read as
+ // a single atomic transaction, but you don't know the size
+ // until you've read the header and so we must reserve the
+ // maximum space even though most messages are a few bytes.
+ // If you read too little, the device returns ENODEV and then no
+ // future reads succeed.
+ while (true) {
+ iovec iov[] = { { &header, sizeof(header) },
+ { message.data(), message.size() } };
+ debug_message("readv iov[0] {{ {}, {} }}",
+ iov[0].iov_base, iov[0].iov_len);
+ debug_message("readv iov[1] {{ {}, {} }}",
+ iov[1].iov_base, iov[1].iov_len);
+
+ ssize_t ret = readv(fd, iov, 2);
+ if (ret > 0) {
+ message.resize(ret - sizeof(header));
+ return { header, std::move(message) };
+ }
+ if (errno != EAGAIN) {
+ throw std::system_error(errno,
+ std::generic_category(),
+ "Failed to read from FUSE device");
+ }
+ }
+ }
+
+ /**
+ * Write an error response to the FUSE daemon. This takes the request's
+ * unique ID and the error code as arguments.
+ */
+ void write_response_error(uint64_t unique, int error)
+ {
+ fuse_out_header out;
+ out.len = sizeof(out);
+ // Ensure that the error value is negative. FUSE uses the Linux
+ // syscall convention where errno is always negative.
+ out.error = -std::abs(error);
+ out.unique = unique;
+ debug_message("Writing error {}", error);
+ ssize_t ret = write(fd, &out, sizeof(out));
+ if (ret < 0) {
+ throw std::system_error(errno, std::generic_category(),
+ "Failed to write to FUSE device");
+ }
+ }
+
+ /**
+ * Write a non-error response. The body can be:
+ *
+ * - A `NoResult` tag type, in which case only the header is sent.
+ * - A (reference to an) object that conforms to the
+ * `IsVariableSizedFuseStructure` concept, in which case the `data()`
+ * and `size()` members are used to extract the payload.
+ * - Any other object type, in which case the object is sent as-is.
+ */
+ template <typename U> void write_response(uint64_t unique, U &body)
+ {
+ // Type of the body, excluding qualifiers.
+ using Body = std::remove_cvref_t<U>;
+ // Is the body type something other than the no-result tag type?
+ constexpr bool HasBody = !std::is_same_v<Body, NoResult>;
+
+ // Construct the header
+ fuse_out_header out;
+ out.error = 0;
+ out.unique = unique;
+ // The size is initially just the header size. This field is
+ // updated if a payload is attached.
+ out.len = sizeof(out);
+
+ // Prepare the iovec.
+ iovec iov[] = { { &out, sizeof(out) },
+ { &body, sizeof(body) } };
+
+ // If this is a variable-sized structure, query it for the data
+ // to send.
+ if constexpr (IsVariableSizedFuseStructure<Body>) {
+ iov[1] = { body.data(), body.size() };
+ out.len += body.size();
+ } else if constexpr (HasBody) {
+ out.len += sizeof(body);
+ }
+
+ // Helper to print in the normal hex dump format. This makes it
+ // easy to compare the messages sent to the kernel against other
+ // implementations (via ktrace).
+ auto print_hex = [](void *ptr, size_t length) {
+ if constexpr (Debug) {
+ uint8_t *p = (uint8_t *)ptr;
+ for (size_t i = 0; i < length; i++) {
+ std::cerr << std::vformat("{:02x}",
+ std::make_format_args(p[i]));
+ if (i % 2 == 1) {
+ std::cerr << ' ';
+ }
+ }
+ std::cerr << std::endl;
+ }
+ };
+ debug_message("Writing response {}, size: {}", unique, out.len);
+ debug_message("iov[0] {{ {}, {} }}", iov[0].iov_base,
+ iov[0].iov_len);
+ print_hex(iov[0].iov_base, iov[0].iov_len);
+ if (HasBody) {
+ debug_message("iov[1] {{ {}, {} }}", iov[1].iov_base,
+ iov[1].iov_len);
+ print_hex(iov[1].iov_base, iov[1].iov_len);
+ }
+
+ // We need to use writev because the FUSE device requires
+ // messages to be written as a single transaction.
+ ssize_t ret = writev(fd, iov, HasBody ? 2 : 1);
+ // FUSE messages are written as a single atomic transaction,
+ // they will never partially fail, the entire write will fail if
+ // the FUSE device does not accept the message. DTrace is your
+ // friend if this happens.
+ if (ret != out.len) {
+ throw std::system_error(errno, std::generic_category(),
+ "Failed to write to FUSE device");
+ }
+ }
+
+ /**
+ * Helper to extract the argument of a callback. This version is never
+ * instantiated, only the specialisation is.
+ */
+ template <typename U> struct CallbackArgumentType { };
+
+ /**
+ * Specialisation for the method pointers that call the function.
+ */
+ template <typename Base, typename ResultType, typename ArgumentType>
+ struct CallbackArgumentType<ErrorOr<ResultType> (
+ Base::*)(const fuse_in_header &, ArgumentType)> {
+ /// The type of the argument.
+ using Argument = std::remove_cvref_t<ArgumentType>;
+ /// The result type.
+ using Result = ErrorOr<ResultType>;
+ /// The result type removing the `ErrorOr` wrapper.
+ using SuccessResultType = ResultType;
+ };
+
+ /**
+ * Specialisation for members that don't take a message-specific
+ * argument type (used for messages where the header is the only
+ * message).
+ */
+ template <typename Base, typename ResultType>
+ struct CallbackArgumentType<ErrorOr<ResultType> (Base::*)(
+ const fuse_in_header &)> {
+ /// The type of the argument.
+ using Argument = void;
+ /// The result type.
+ using Result = ErrorOr<ResultType>;
+ /// The result type removing the `ErrorOr` wrapper.
+ using SuccessResultType = ResultType;
+ };
+
+ /**
+ * Dispatch a message to the handler given by `function`. This must be
+ * a member pointer for `T`. The body will be coerced to the type that
+ * this function expects and then the response sent back to the kernel.
+ * The return type is expected to be an `ErrorOr` wrapper around the
+ * real return result. If this contains the error code, that will be
+ * reported as an error, otherwise the payload will be returned.
+ *
+ * If the return type is the `NoResponse` tag type, no response is sent
+ * (not even the header).
+ */
+ void dispatch(auto function, const fuse_in_header header,
+ const Buffer body)
+ {
+ // Extract the argument and result types from the pointer
+ using Argument =
+ typename CallbackArgumentType<decltype(function)>::Argument;
+ using Result =
+ typename CallbackArgumentType<decltype(function)>::Result;
+
+ // Call the callback and get the result
+ Result result;
+ if constexpr (std::is_same_v<Argument, void>) {
+ // If this doesn't take an extra argument, use the
+ // one-argument version.
+ result = ((static_cast<T *>(this))->*function)(header);
+ } else if constexpr (std::is_pointer_v<Argument>) {
+ // If the argument is a pointer, pass it directly.
+ result = ((static_cast<T *>(this))->*function)(header,
+ reinterpret_cast<const Argument>(body.data()));
+ } else {
+ // If the argument type is not a pointer, convert the
+ // pointer to a reference of the correct type and pass
+ // that.
+ const Argument &argument =
+ *reinterpret_cast<const Argument *>(body.data());
+ result = ((static_cast<T *>(this))->*function)(header,
+ argument);
+ }
+ // Write the response back to the kernel.
+ auto unique = header.unique;
+ std::visit(
+ [unique, this](auto &&arg) {
+ if constexpr (std::is_same_v<int,
+ std::remove_cvref_t<
+ decltype(arg)>>) {
+ write_response_error(unique, arg);
+ } else if (!std::is_same_v<NoResponse,
+ std::remove_cvref_t<
+ decltype(arg)>>) {
+ write_response(unique, arg);
+ }
+ },
+ result);
+ }
+
+ public:
+ /**
+ * Tag type for results where the FUSE response header is returned to
+ * the kernel with no additional data.
+ */
+ struct NoResult { };
+
+ /**
+ * Tag type for when no response should be given for a FUSE message.
+ */
+ struct NoResponse { };
+
+ /**
+ * Helper for building variable-sized responses.
+ */
+ struct VariableSizeResponse : Buffer {
+ /**
+ * Add an arbitrary object to this buffer.
+ */
+ template <typename U>
+ VariableSizeResponse &operator<<(const U &object)
+ requires(std::is_trivially_copyable_v<U>)
+ {
+ const uint8_t *start =
+ reinterpret_cast<const uint8_t *>(&object);
+ const uint8_t *end = start + sizeof(U);
+ Buffer::insert(Buffer::end(), start, end);
+ return *this;
+ }
+
+ /**
+ * Add a string view to this buffer.
+ */
+ VariableSizeResponse &operator<<(std::string_view string)
+ {
+ Buffer::insert(Buffer::end(), string.begin(),
+ string.end());
+ return *this;
+ }
+
+ /**
+ * Insert padding to ensure alignment (at the end, `size()` will
+ * return a multiple of `align`).
+ */
+ void pad_to_alignment(size_t align)
+ {
+ while (size() % align != 0) {
+ push_back(0);
+ }
+ }
+ };
+
+ /**
+ * Constructor. Takes ownership of the filesystem given to us by
+ * `mount_fusefs`.
+ */
+ FuseFS()
+ {
+ std::string fdName = getenv("FUSE_DEV_FD");
+ if (fdName.empty()) {
+ throw std::invalid_argument("FUSE_DEV_FD not set");
+ }
+ fd = std::stoi(fdName);
+ }
+
+ /**
+ * Destructor.
+ */
+ ~FuseFS()
+ {
+ if (fd != -1) {
+ close(fd);
+ }
+ }
+
+ /**
+ * Default handler for FUSE_INIT messages. Sets some sensible defaults
+ * for the connection. You can override this and either call this and
+ * modify the result, or replace it entirely.
+ */
+ ErrorOr<fuse_init_out> fuse_init(const fuse_in_header &,
+ const fuse_init_in &initIn)
+ {
+ // If this is a very old FUSE version, give up.
+ if ((initIn.major < 7) ||
+ ((initIn.major == 7) && (initIn.minor < 13))) {
+ return ENOSYS;
+ }
+ fuse_init_out reply;
+ // Make sure any new fields are zero.
+ memset(&reply, 0, sizeof(reply));
+ reply.major = FUSE_KERNEL_VERSION;
+ reply.minor = FUSE_KERNEL_MINOR_VERSION;
+ reply.max_readahead = MaxReadSize;
+ reply.congestion_threshold = 100;
+ reply.max_background = 100;
+ reply.max_write = MaxWriteSize;
+ reply.time_gran = 1;
+ // If the subclass doesn't override fuse_open and fuse_opendir,
+ // ask the kernel not to call them.
+ if constexpr (&T::fuse_open == &FuseFS<T, Debug>::fuse_open) {
+ reply.flags |= FUSE_NO_OPEN_SUPPORT;
+ }
+ if constexpr (&T::fuse_opendir ==
+ &FuseFS<T, Debug>::fuse_opendir) {
+ reply.flags |= FUSE_NO_OPENDIR_SUPPORT;
+ }
+ debug_message("Initialised FUSE connection!");
+ return reply;
+ }
+
+ /**
+ * Handler for FUSE_GETATTR messages. Should be overridden by
+ * subclasses.
+ */
+ ErrorOr<fuse_attr_out> fuse_getattr(const fuse_in_header &,
+ const fuse_getattr_in &)
+ {
+ return ENOSYS;
+ }
+
+ /**
+ * Handler for FUSE_OPEN messages. Should be overridden by subclasses.
+ */
+ ErrorOr<fuse_open_out> fuse_open(const fuse_in_header &,
+ const fuse_open_in &)
+ {
+ return ENOSYS;
+ }
+
+ /**
+ * Handler for FUSE_OPENDIR messages. Should be overridden by
+ * subclasses.
+ */
+ ErrorOr<fuse_open_out> fuse_opendir(const fuse_in_header &,
+ const fuse_open_in &)
+ {
+ return ENOSYS;
+ }
+
+ /**
+ * Handler for FUSE_ACCESS messages. This can return an error if access
+ * is not allowed, otherwise the VFS layer will perform normal access
+ * checks.
+ */
+ ErrorOr<NoResult> fuse_access(const fuse_in_header &,
+ const fuse_access_in &)
+ {
+ // Allow anything that VFS is happy with.
+ return ENOSYS;
+ }
+
+ /**
+ * Handler for FUSE_RELEASE messages. Should be overridden by
+ * subclasses that store any state per file descriptor.
+ */
+ ErrorOr<NoResult> fuse_release(const fuse_in_header &,
+ const fuse_release_in &)
+ {
+ return NoResult {};
+ }
+
+ /**
+ * Handler for FUSE_OPENDIR messages. Should be overridden by
+ * subclasses that store any state per directory descriptor.
+ */
+ ErrorOr<NoResult> fuse_releasedir(const fuse_in_header &,
+ const fuse_release_in &)
+ {
+ return NoResult {};
+ }
+
+ /**
+ * Handler for FUSE_FORGET messages. Can be overridden by subclasses.
+ */
+ ErrorOr<NoResponse> fuse_forget(const fuse_in_header &,
+ const fuse_forget_in &)
+ {
+ return NoResponse {};
+ }
+
+ /**
+ * Handler for FUSE_READDIR messages. Should be overridden by
+ * subclasses.
+ *
+ * The result is a sequence of `fuse_dirent` structures, followed by the
+ * name of the entry, padded to an 8-byte boundary.
+ */
+ ErrorOr<VariableSizeResponse> fuse_readdir(const fuse_in_header &,
+ const fuse_read_in &)
+ {
+ return ENOSYS;
+ }
+
+ /**
+ * Handler for FUSE_READ messages. Should be overridden by subclasses.
+ */
+ ErrorOr<VariableSizeResponse> fuse_read(const fuse_in_header &,
+ const fuse_read_in &)
+ {
+ return ENOSYS;
+ }
+
+ /**
+ * Handler for FUSE_LOOKUP messages. Should be overridden by
+ * subclasses.
+ */
+ ErrorOr<fuse_entry_out> fuse_lookup(const fuse_in_header &,
+ const char *)
+ {
+ return ENOSYS;
+ }
+
+ /**
+ * Handler for FUSE_FLUSH messages. Can be overridden by subclasses.
+ *
+ * The default implementation instructs the kernel to not send any
+ * future flush requests.
+ */
+ ErrorOr<NoResult> fuse_flush(const fuse_in_header &,
+ const fuse_flush_in &)
+ {
+ return ENOSYS;
+ }
+
+ /**
+ * Handler for FUSE_SETATTR messages. This is needed even for read-only
+ * filesystems unless they are mounted with noatime. The default
+ * implementation returns a stub set of attributes with a cache policy
+ * indicating that they are immediately invalidated so the kernel will
+ * then query the daemon again to get the real values. This is
+ * sufficient for read-only filesystems that don't support atime.
+ */
+ ErrorOr<fuse_attr_out> fuse_setattr(const fuse_in_header &,
+ const fuse_setattr_in &)
+ {
+ return ENOSYS;
+ }
+
+ /**
+ * Default statfs. Sets some defaults. Can be overwritten and this
+ * version can be called to provide some defaults that a filesystem then
+ * modifies.
+ */
+ ErrorOr<fuse_statfs_out> fuse_statfs(const fuse_in_header &)
+ {
+ fuse_statfs_out out;
+ memset(&out, 0, sizeof(out));
+ out.st.blocks = 1;
+ out.st.bfree = 0;
+ out.st.bavail = 0;
+ out.st.files = 0;
+ out.st.ffree = 0;
+ // Default block size
+ out.st.bsize = 512;
+ out.st.namelen = PATH_MAX;
+ out.st.frsize = 0;
+ return out;
+ }
+
+ /**
+ * Default block map. Just maps the input block to the same output.
+ */
+ ErrorOr<fuse_bmap_out> fuse_bmap(const fuse_in_header &,
+ const fuse_bmap_in &)
+ {
+ return ENOSYS;
+ }
+
+ /*
+ * Enter a run loop, waiting for kernel messages and posting responses.
+ *
+ * When a FUSE message is received from the kernel, this calls the
+ * corresponding handler in the subclass (or this class if none is
+ * provided in the subclass).
+ */
+ void run()
+ {
+ debug_message("Starting FUSE FS");
+ bool destroy = false;
+ while (!destroy) {
+ auto [header, body] = read_message();
+
+ debug_message(
+ "Message: {{ opcode: {}, length: {}, unique: {}, nodeid: {}, uid: {}, gid: {}, pid: {} }}",
+ header.opcode, header.len, header.unique,
+ header.nodeid, header.uid, header.gid, header.pid);
+ switch (header.opcode) {
+ default:
+ debug_message(
+ "Unhandled message with opcode {}",
+ header.opcode);
+ write_response_error(header.unique, ENOSYS);
+ break;
+ case FUSE_INIT:
+ dispatch(&T::fuse_init, header, body);
+ break;
+ case FUSE_GETATTR:
+ dispatch(&T::fuse_getattr, header, body);
+ break;
+ case FUSE_OPEN:
+ dispatch(&T::fuse_open, header, body);
+ break;
+ case FUSE_OPENDIR:
+ dispatch(&T::fuse_opendir, header, body);
+ break;
+ case FUSE_ACCESS:
+ dispatch(&T::fuse_access, header, body);
+ break;
+ case FUSE_READDIR:
+ dispatch(&T::fuse_readdir, header, body);
+ break;
+ case FUSE_READ:
+ dispatch(&T::fuse_read, header, body);
+ break;
+ case FUSE_FORGET:
+ dispatch(&T::fuse_forget, header, body);
+ break;
+ case FUSE_RELEASEDIR:
+ dispatch(&T::fuse_releasedir, header, body);
+ break;
+ case FUSE_RELEASE:
+ dispatch(&T::fuse_release, header, body);
+ break;
+ case FUSE_LOOKUP:
+ dispatch(&T::fuse_lookup, header, body);
+ break;
+ case FUSE_FLUSH:
+ dispatch(&T::fuse_flush, header, body);
+ break;
+ case FUSE_SETATTR:
+ dispatch(&T::fuse_setattr, header, body);
+ break;
+ case FUSE_STATFS:
+ dispatch(&T::fuse_statfs, header, body);
+ break;
+ case FUSE_BMAP:
+ dispatch(&T::fuse_bmap, header, body);
+ break;
+ case FUSE_DESTROY:
+ // When we receive a destroy message, this
+ // filesystem has been unmounted. This doesn't
+ // need a response, just close the device and
+ // exit the run loop.
+ close(fd);
+ fd = -1;
+ destroy = true;
+ break;
+ }
+ }
+ }
+};
+
+}
diff --git a/sys/dev/ic/qemufwcfgio.h b/sys/dev/ic/qemufwcfgio.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/ic/qemufwcfgio.h
@@ -0,0 +1,42 @@
+/* $NetBSD: qemufwcfgio.h,v 1.1 2017/11/25 16:31:03 jmcneill Exp $ */
+
+/*-
+ * Copyright (c) 2017 Jared McNeill <jmcneill@invisible.ca>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _QEMUFWCFGIO_H
+#define _QEMUFWCFGIO_H
+
+#include <sys/ioccom.h>
+
+/* Fixed selector keys */
+#define FW_CFG_SIGNATURE 0x0000 /* Signature */
+#define FW_CFG_ID 0x0001 /* Revision / feature bitmap */
+#define FW_CFG_FILE_DIR 0x0019 /* File directory */
+#define FW_CFG_FILE_FIRST 0x0020 /* First file in directory */
+
+#define FWCFGIO_SET_INDEX _IOW('q', 0, uint16_t)
+
+#endif /* !_QEMUFWCFGIO_H */
diff --git a/sys/dev/qemufwcfg/qemufwcfg.5 b/sys/dev/qemufwcfg/qemufwcfg.5
new file mode 100644
--- /dev/null
+++ b/sys/dev/qemufwcfg/qemufwcfg.5
@@ -0,0 +1,77 @@
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.\" Copyright (c) 2023 David Chisnall <theraven@FreeBSD.org>
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.Dd August 17, 2023
+.Dt QEMUFWCFG 5
+.Os
+.Sh NAME
+.Nm qemufqcfg
+.Nd "QEMU Firmware Config client interface"
+.Sh SYNOPSIS
+.Pp
+To load as a loadable kernel module:
+.Pp
+.Dl "kldload fusefs"
+.Sh DESCRIPTION
+The
+.Nm
+driver implements the client driver for QEMU's firmware configuration interface.
+.Pp
+This interface allows QEMU, or a compatible emulator or hypervisor such as
+.Xr bhyve 8 ,
+to provide configuration files via a simple interface.
+The core interface is a simple (read-only) key-value store that uses 16-bit integers as keys and bytes streams as values.
+.Pp
+This driver is intended to be used in conjunction with a single userspace client, such as the
+.Xr mount_qemufwcfg 8
+FUSE filesystem.
+.Pp
+The driver supports a single command via the
+.Xr ioctl 2
+system call:
+.Dv FWCFGIO_SET_INDEX .
+This takes a 16-bit integer as the argument and selects the specified entry in the host.
+Subsequent
+.Xr read 2
+calls will read the byte stream associated with that selector.
+.Sh SEE ALSO
+.Rs
+.Xr mount_qemufwcfg 8 ,
+.%T QEMU Firmware Configuration (fw_cfg) Device specification
+.%U https://www.qemu.org/docs/master/specs/fw_cfg.html
+.Sh HISTORY
+The
+.Nm qemufqcfg
+driver first appeared in NetBSD 9.0.
+The
+.Fx
+implementation, which used the NetBSD version as a reference, first appeared in
+.Fx 14.0
+.
+.Sh AUTHORS
+The
+.Nm qemufqcfg
+driver was originally written by
+.An David Chisnall .
diff --git a/sys/dev/qemufwcfg/qemufwcfg.c b/sys/dev/qemufwcfg/qemufwcfg.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/qemufwcfg/qemufwcfg.c
@@ -0,0 +1,389 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2023 David Chisnall <theraven@FreeBSD.org>
+ *
+ * This file was created using the NetBSD implementation as reference and so
+ * may be a derived work of the NetBSD implementation:
+ * Copyright (c) 2017 Jared McNeill <jmcneill@invisible.ca>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/**
+ * This provides a NetBSD-compatible qemufwcfg device.
+ *
+ * The only intended consumer of this is a FUSE filesystem that exports the
+ * firmware configuration to userspace.
+ */
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/rman.h>
+#include <sys/stddef.h>
+#include <sys/uio.h>
+
+#include <machine/bus.h>
+#include <machine/resource.h>
+
+#include <dev/ic/qemufwcfgio.h>
+
+// clang-format off
+// Clang-format puts these in the wrong order.
+#include <contrib/dev/acpica/include/acpi.h>
+#include <contrib/dev/acpica/include/accommon.h>
+#include <dev/acpica/acpivar.h>
+// clang-format on
+
+// Forward declarations
+static int qemufwcfg_probe(device_t);
+static int qemufwcfg_attach(device_t);
+static int qemufwcfg_detach(device_t);
+static int qemufwcfg_open(struct cdev *dev, int oflags, int devtype,
+ struct thread *td);
+static int qemufwcfg_close(struct cdev *dev, int flags, int fmt,
+ struct thread *td);
+static int qemufwcfg_ioctl(struct cdev *dev, u_long cmd, caddr_t data,
+ int fflag, struct thread *td);
+static int qemufwcfg_read(struct cdev *dev, struct uio *uio, int ioflag);
+
+/**
+ * Device attachment methods.
+ */
+static device_method_t qemufwcfg_methods[] = {
+ /* Methods from the device interface */
+ DEVMETHOD(device_probe, qemufwcfg_probe),
+ DEVMETHOD(device_attach, qemufwcfg_attach),
+ DEVMETHOD(device_detach, qemufwcfg_detach),
+ /* Terminate method list */
+ DEVMETHOD_END
+};
+
+/**
+ * Character device methods.
+ */
+static struct cdevsw qemufwcfg_cdevsw = {
+ .d_version = D_VERSION,
+ .d_name = "qemufwcfg",
+ .d_open = qemufwcfg_open,
+ .d_close = qemufwcfg_close,
+ .d_read = qemufwcfg_read,
+ .d_ioctl = qemufwcfg_ioctl,
+};
+
+/**
+ * State for this device.
+ */
+struct qemufwcfg_softc {
+ /// I/O type, MMIO or I/O port
+ int io_type;
+ /// Resource id, filled in by `bus_alloc_resource_any`.
+ int resource_id;
+ /// The resource for this device, from ACPI. May be I/O ports on x86
+ /// or MMIO.
+ struct resource *res;
+ /// Cached copy of the bus space tag from `res`.
+ bus_space_tag_t tag;
+ /// Cached copy of the bus space handle from `res`.
+ bus_space_handle_t handle;
+ /// Character device node.
+ struct cdev *cdev;
+ /// Mutex protecting this structure. This is used to protect `is_open`.
+ struct mtx mutex;
+ /// Flag indicating that this is open, protects against concurrent
+ /// access.
+ bool is_open;
+};
+
+/**
+ * Driver configuration.
+ */
+static driver_t qemufwcfg_driver = { "qemufwcfg", qemufwcfg_methods,
+ sizeof(struct qemufwcfg_softc) };
+
+DRIVER_MODULE(qemufwcfg, acpi, qemufwcfg_driver, NULL, NULL);
+
+/*
+ * The selector reserved for checking that this is the correct
+ * interface.
+ */
+const int SignatureSelector = 0;
+
+/**
+ * Helper. Writes the specified selector value to the device.
+ */
+static void
+write_selector(struct qemufwcfg_softc *sc, uint16_t index)
+{
+ // The offset from the base I/O port when using I/O ports
+ const int SelectorPortOffset = 0x0;
+ // The offset from the base MMIO address, when using MMIO.
+ const int SelectorMMIOOffset = 0x8;
+ int offset;
+
+ if (sc->io_type == SYS_RES_IOPORT) {
+ // I/O port mode uses little endian for the selector
+ index = htole16(index);
+ offset = SelectorPortOffset;
+ } else {
+ // MMIO mode uses little endian for the selector
+ index = htobe16(index);
+ offset = SelectorMMIOOffset;
+ }
+ // Write the selector value.
+ bus_space_write_2(sc->tag, sc->handle, offset, index);
+}
+
+/**
+ * Helper. Returns the offset for the selector.
+ */
+static int
+offset_for_data(struct qemufwcfg_softc *sc)
+{
+ // The offset from the base I/O port when using I/O ports
+ const int DataPortOffset = 0x1;
+ // The offset from the base MMIO address, when using MMIO.
+ const int DataMMIOOffset = 0x0;
+ return (
+ (sc->io_type == SYS_RES_IOPORT) ? DataPortOffset : DataMMIOOffset);
+}
+
+/**
+ * Probe hook. Checks that the ACPI node exists.
+ */
+static int
+qemufwcfg_probe(device_t dev)
+{
+ ACPI_HANDLE h;
+
+ if ((h = acpi_get_handle(dev)) == NULL)
+ return (ENXIO);
+
+ if (!acpi_MatchHid(h, "QEMU0002"))
+ return (ENXIO);
+
+ return (0);
+}
+
+/**
+ * Attach to the device. This performs a read on the signature to ensure that
+ * this really is the right kind of device.
+ */
+static int
+qemufwcfg_attach(device_t dev)
+{
+ struct qemufwcfg_softc *sc = device_get_softc(dev);
+
+ // Try to configure the memory space. The device can use I/O ports on
+ // x86, memory elsewhere.
+ if (bus_get_resource(dev, SYS_RES_IOPORT, 0, NULL, NULL) == 0) {
+ sc->io_type = SYS_RES_IOPORT;
+ } else if (bus_get_resource(dev, SYS_RES_MEMORY, 0, NULL, NULL) == 0) {
+ sc->io_type = SYS_RES_MEMORY;
+ } else {
+ device_printf(dev, "Unknown resource type\n");
+ return (ENXIO);
+ }
+
+ sc->res = bus_alloc_resource_any(dev, sc->io_type, &sc->resource_id,
+ RF_ACTIVE);
+ if (sc->res == NULL) {
+ device_printf(dev, "Failed to allocate bus resource\n");
+ return (ENXIO);
+ }
+
+ // Cache the tag and handle so we don't have to keep looking them up.
+ sc->tag = rman_get_bustag(sc->res);
+ sc->handle = rman_get_bushandle(sc->res);
+
+ write_selector(sc, SignatureSelector);
+
+ // Read 4 bytes from signature.
+ int offset = offset_for_data(sc);
+ char buf[4];
+ bus_space_read_multi_1(sc->tag, sc->handle, offset, buf, sizeof(buf));
+
+ // Check that the signature value is correct.
+ static const char expected[] = "QEMU";
+ _Static_assert(sizeof(expected) >= sizeof(buf),
+ "Expected value too small!");
+ if (strncmp(buf, expected, sizeof(buf)) != 0) {
+ bus_release_resource(dev, sc->io_type, sc->resource_id,
+ sc->res);
+ sc->res = NULL;
+ device_printf(dev,
+ "Failed to attach, got <%c%c%c%c>, expected <QEMU>\n",
+ buf[0], buf[1], buf[2], buf[3]);
+ return (ENXIO);
+ }
+
+ mtx_init(&sc->mutex, "qemufwcfg lock", NULL, MTX_DEF);
+
+ // Create the device node.
+ struct make_dev_args args;
+ make_dev_args_init(&args);
+ args.mda_mode = 0400;
+ args.mda_devsw = &qemufwcfg_cdevsw;
+ args.mda_si_drv1 = sc;
+ args.mda_flags = MAKEDEV_WAITOK | MAKEDEV_CHECKNAME;
+
+ int ret = make_dev_s(&args, &sc->cdev, "qemufwcfg");
+ KASSERT(ret == 0, "make_dev_s should not fail here");
+
+ return (0);
+}
+
+/**
+ * Detach from the device. Cleans up resources.
+ */
+static int
+qemufwcfg_detach(device_t dev)
+{
+ struct qemufwcfg_softc *sc = device_get_softc(dev);
+ destroy_dev(sc->cdev);
+ bus_release_resource(dev, sc->io_type, sc->resource_id, sc->res);
+ mtx_destroy(&sc->mutex);
+ return (0);
+}
+
+/**
+ * Open. This device doesn't allow concurrent access so this fails if more
+ * more than one attempt is made to open the device.
+ */
+static int
+qemufwcfg_open(struct cdev *dev, int oflags __unused, int devtype __unused,
+ struct thread *td __unused)
+{
+ struct qemufwcfg_softc *sc = dev->si_drv1;
+ int error = 0;
+
+ mtx_lock(&sc->mutex);
+ if (sc->is_open) {
+ error = EBUSY;
+ } else {
+ sc->is_open = true;
+ }
+ mtx_unlock(&sc->mutex);
+
+ return (error);
+}
+
+/**
+ * Close the device. This just marks the device as not open to allow another
+ * userspace process to open it, it doesn't do any cleanup.
+ */
+static int
+qemufwcfg_close(struct cdev *dev, int flags __unused, int fmt __unused,
+ struct thread *td __unused)
+{
+ struct qemufwcfg_softc *sc = dev->si_drv1;
+ int error = 0;
+
+ mtx_lock(&sc->mutex);
+ if (!sc->is_open) {
+ error = EINVAL;
+ } else {
+ sc->is_open = false;
+ }
+ mtx_unlock(&sc->mutex);
+
+ return (error);
+}
+
+/**
+ * Ioctl handler. A single ioctl is supported, to set the selector.
+ */
+static int
+qemufwcfg_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag __unused,
+ struct thread *td __unused)
+{
+ struct qemufwcfg_softc *sc = dev->si_drv1;
+
+ switch (cmd) {
+ default:
+ return (ENOTTY);
+ case FWCFGIO_SET_INDEX: {
+ uint16_t index = *(uint16_t *)data;
+ write_selector(sc, index);
+
+ return (0);
+ }
+ }
+}
+
+/**
+ * Read. This reads the specified number of bytes from the currently
+ * configured selector. Seek is not supported (here or by the QEMU device),
+ * the only way of reading backwards is to reset to the beginning of a 'file'
+ * and read forwards.
+ *
+ * DMA is not currently used. For small files, the cost of pinning a buffer
+ * and passing a physical address out to the host would likely offset any
+ * speedup. We can read 8 bytes at a time and most files that we read are a
+ * handful of MMIO reads at this size.
+ */
+static int
+qemufwcfg_read(struct cdev *dev, struct uio *uio, int ioflag __unused)
+{
+ struct qemufwcfg_softc *sc = dev->si_drv1;
+
+ if (sc == NULL) {
+ return (ENXIO);
+ }
+
+ int error = 0;
+ int offset = offset_for_data(sc);
+
+#if !(defined(__i386__) || defined(__amd64__))
+ // If we're in MMIO mode, try reading 8 bytes at a time. This reduces
+ // the number of VM exits that we need by a factor of 8, which is
+ // probably premature optimisation given how rare reads on this device
+ // are, but was easy to do.
+ if (sc->io_type == SYS_RES_MEMORY) {
+ uint64_t buf[8];
+ while ((uio->uio_resid > sizeof(buf[0])) && (error == 0)) {
+ size_t count = qmin(sizeof(buf), uio->uio_resid) /
+ sizeof(buf[0]);
+ bus_space_read_multi_8(sc->tag, sc->handle, offset, buf,
+ count);
+ error = uiomove(buf, count * sizeof(buf[0]), uio);
+ }
+ }
+#endif
+
+ while ((uio->uio_resid > 0) && (error == 0)) {
+ // Try copying 64 bytes at a time. If we're on a platform that
+ // supports MMIO then we should be copying at most 7 bytes here
+ // because we'll have read the rest via 8-byte reads. If we're
+ // using x86 IO Ports then we have to read one byte at at time.
+ uint8_t buf[64];
+ size_t count = qmin(sizeof(buf), uio->uio_resid);
+ bus_space_read_multi_1(sc->tag, sc->handle, offset, buf, count);
+ error = uiomove(buf, count, uio);
+ }
+
+ return (error);
+}
diff --git a/sys/modules/Makefile b/sys/modules/Makefile
--- a/sys/modules/Makefile
+++ b/sys/modules/Makefile
@@ -331,6 +331,7 @@
${_qatfw} \
${_qat_c2xxx} \
${_qat_c2xxxfw} \
+ qemufwcfg \
${_qlxge} \
${_qlxgb} \
${_qlxgbe} \
diff --git a/sys/modules/qemufwcfg/Makefile b/sys/modules/qemufwcfg/Makefile
new file mode 100644
--- /dev/null
+++ b/sys/modules/qemufwcfg/Makefile
@@ -0,0 +1,8 @@
+.PATH: ${SRCTOP}/sys/dev/qemufwcfg
+
+KMOD= qemufwcfg
+
+SRCS= qemufwcfg.c device_if.h bus_if.h acpi_if.h opt_acpi.h
+
+.include <bsd.kmod.mk>
+

File Metadata

Mime Type
text/plain
Expires
Thu, Jan 30, 9:27 PM (5 h, 45 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
16343585
Default Alt Text
D45993.id141117.diff (63 KB)

Event Timeline