[PATCH 00/11] virtio: Add a fully functional virtio emulator

From: Simon Glass <sjg@chromium.org> The current implementation of virtio in sandbox is fairly basic. It is enough to test the behaviour of queues, but it does not test the full stack, e.g. using MMIO to access a block device. This series adds a new type of virtio emulator which is capable of handling MMIO. Some support for MMIO is added to sandbox in the process. With this, a dummy block device can be used from within U-Boot, without running on QEMU Simon Glass (11): mbed: Remove check_files.py test: Unset make variables before running make again dm: Move UCLASS_FFA_EMUL into the correct place alist: Allow inclusion from OS headers sandbox: Move memory-related functions to a separate file sandbox: Support memory-mapped I/O virtio: Move bindings for virtio IDs to dt-bindings virtio: Export a few things from virtio_mmio.c virtio: Implement a proper sandbox emulator virtio: Implement a simple block-device emulator sandbox: Enable the new virtio emulator arch/Kconfig | 1 + arch/sandbox/cpu/Makefile | 2 +- arch/sandbox/cpu/cpu.c | 274 +-------- arch/sandbox/cpu/mem.c | 337 +++++++++++ arch/sandbox/cpu/state.c | 2 + arch/sandbox/dts/test.dts | 13 +- arch/sandbox/include/asm/io.h | 15 + arch/sandbox/include/asm/state.h | 67 +++ configs/sandbox_defconfig | 1 + configs/tools-only_defconfig | 2 +- drivers/virtio/Kconfig | 8 + drivers/virtio/Makefile | 1 + drivers/virtio/emul_blk.c | 153 +++++ drivers/virtio/sandbox_emul.c | 313 ++++++++++ drivers/virtio/sandbox_emul.h | 110 ++++ drivers/virtio/virtio_blk.h | 3 + drivers/virtio/virtio_internal.h | 19 + drivers/virtio/virtio_mmio.c | 7 +- include/alist.h | 16 +- include/dm/uclass-id.h | 3 +- include/dt-bindings/virtio.h | 19 + include/virtio.h | 5 +- lib/alist.c | 1 + .../mbedtls/tests/scripts/check_files.py | 537 ------------------ test/run | 3 + 25 files changed, 1085 insertions(+), 827 deletions(-) create mode 100644 arch/sandbox/cpu/mem.c create mode 100644 drivers/virtio/emul_blk.c create mode 100644 drivers/virtio/sandbox_emul.c create mode 100644 drivers/virtio/sandbox_emul.h create mode 100644 drivers/virtio/virtio_internal.h create mode 100644 include/dt-bindings/virtio.h delete mode 100755 lib/mbedtls/external/mbedtls/tests/scripts/check_files.py -- 2.43.0 base-commit: f737237d29a57cc7fe16c6e4b5e67e0df57d87f3 branch: qemd

From: Simon Glass <sjg@chromium.org> This script has conflict markers in it so comes up as an unwanted match when searching them. Remove the file since it is not used in U-Boot. Signed-off-by: Simon Glass <sjg@chromium.org> --- .../mbedtls/tests/scripts/check_files.py | 537 ------------------ 1 file changed, 537 deletions(-) delete mode 100755 lib/mbedtls/external/mbedtls/tests/scripts/check_files.py diff --git a/lib/mbedtls/external/mbedtls/tests/scripts/check_files.py b/lib/mbedtls/external/mbedtls/tests/scripts/check_files.py deleted file mode 100755 index d5a4b921e4f..00000000000 --- a/lib/mbedtls/external/mbedtls/tests/scripts/check_files.py +++ /dev/null @@ -1,537 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright The Mbed TLS Contributors -# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later - -""" -This script checks the current state of the source code for minor issues, -including incorrect file permissions, presence of tabs, non-Unix line endings, -trailing whitespace, and presence of UTF-8 BOM. -Note: requires python 3, must be run from Mbed TLS root. -""" - -import argparse -import codecs -import inspect -import logging -import os -import re -import subprocess -import sys -try: - from typing import FrozenSet, Optional, Pattern # pylint: disable=unused-import -except ImportError: - pass - -import scripts_path # pylint: disable=unused-import -from mbedtls_dev import build_tree - - -class FileIssueTracker: - """Base class for file-wide issue tracking. - - To implement a checker that processes a file as a whole, inherit from - this class and implement `check_file_for_issue` and define ``heading``. - - ``suffix_exemptions``: files whose name ends with a string in this set - will not be checked. - - ``path_exemptions``: files whose path (relative to the root of the source - tree) matches this regular expression will not be checked. This can be - ``None`` to match no path. Paths are normalized and converted to ``/`` - separators before matching. - - ``heading``: human-readable description of the issue - """ - - suffix_exemptions = frozenset() #type: FrozenSet[str] - path_exemptions = None #type: Optional[Pattern[str]] - # heading must be defined in derived classes. - # pylint: disable=no-member - - def __init__(self): - self.files_with_issues = {} - - @staticmethod - def normalize_path(filepath): - """Normalize ``filepath`` with / as the directory separator.""" - filepath = os.path.normpath(filepath) - # On Windows, we may have backslashes to separate directories. - # We need slashes to match exemption lists. - seps = os.path.sep - if os.path.altsep is not None: - seps += os.path.altsep - return '/'.join(filepath.split(seps)) - - def should_check_file(self, filepath): - """Whether the given file name should be checked. - - Files whose name ends with a string listed in ``self.suffix_exemptions`` - or whose path matches ``self.path_exemptions`` will not be checked. - """ - for files_exemption in self.suffix_exemptions: - if filepath.endswith(files_exemption): - return False - if self.path_exemptions and \ - re.match(self.path_exemptions, self.normalize_path(filepath)): - return False - return True - - def check_file_for_issue(self, filepath): - """Check the specified file for the issue that this class is for. - - Subclasses must implement this method. - """ - raise NotImplementedError - - def record_issue(self, filepath, line_number): - """Record that an issue was found at the specified location.""" - if filepath not in self.files_with_issues.keys(): - self.files_with_issues[filepath] = [] - self.files_with_issues[filepath].append(line_number) - - def output_file_issues(self, logger): - """Log all the locations where the issue was found.""" - if self.files_with_issues.values(): - logger.info(self.heading) - for filename, lines in sorted(self.files_with_issues.items()): - if lines: - logger.info("{}: {}".format( - filename, ", ".join(str(x) for x in lines) - )) - else: - logger.info(filename) - logger.info("") - -BINARY_FILE_PATH_RE_LIST = [ - r'docs/.*\.pdf\Z', - r'docs/.*\.png\Z', - r'programs/fuzz/corpuses/[^.]+\Z', - r'tests/data_files/[^.]+\Z', - r'tests/data_files/.*\.(crt|csr|db|der|key|pubkey)\Z', - r'tests/data_files/.*\.req\.[^/]+\Z', - r'tests/data_files/.*malformed[^/]+\Z', - r'tests/data_files/format_pkcs12\.fmt\Z', - r'tests/data_files/.*\.bin\Z', -] -BINARY_FILE_PATH_RE = re.compile('|'.join(BINARY_FILE_PATH_RE_LIST)) - -class LineIssueTracker(FileIssueTracker): - """Base class for line-by-line issue tracking. - - To implement a checker that processes files line by line, inherit from - this class and implement `line_with_issue`. - """ - - # Exclude binary files. - path_exemptions = BINARY_FILE_PATH_RE - - def issue_with_line(self, line, filepath, line_number): - """Check the specified line for the issue that this class is for. - - Subclasses must implement this method. - """ - raise NotImplementedError - - def check_file_line(self, filepath, line, line_number): - if self.issue_with_line(line, filepath, line_number): - self.record_issue(filepath, line_number) - - def check_file_for_issue(self, filepath): - """Check the lines of the specified file. - - Subclasses must implement the ``issue_with_line`` method. - """ - with open(filepath, "rb") as f: - for i, line in enumerate(iter(f.readline, b"")): - self.check_file_line(filepath, line, i + 1) - - -def is_windows_file(filepath): - _root, ext = os.path.splitext(filepath) - return ext in ('.bat', '.dsp', '.dsw', '.sln', '.vcxproj') - - -class ShebangIssueTracker(FileIssueTracker): - """Track files with a bad, missing or extraneous shebang line. - - Executable scripts must start with a valid shebang (#!) line. - """ - - heading = "Invalid shebang line:" - - # Allow either /bin/sh, /bin/bash, or /usr/bin/env. - # Allow at most one argument (this is a Linux limitation). - # For sh and bash, the argument if present must be options. - # For env, the argument must be the base name of the interpreter. - _shebang_re = re.compile(rb'^#! ?(?:/bin/(bash|sh)(?: -[^\n ]*)?' - rb'|/usr/bin/env ([^\n /]+))$') - _extensions = { - b'bash': 'sh', - b'perl': 'pl', - b'python3': 'py', - b'sh': 'sh', - } - - path_exemptions = re.compile(r'tests/scripts/quiet/.*') - - def is_valid_shebang(self, first_line, filepath): - m = re.match(self._shebang_re, first_line) - if not m: - return False - interpreter = m.group(1) or m.group(2) - if interpreter not in self._extensions: - return False - if not filepath.endswith('.' + self._extensions[interpreter]): - return False - return True - - def check_file_for_issue(self, filepath): - is_executable = os.access(filepath, os.X_OK) - with open(filepath, "rb") as f: - first_line = f.readline() - if first_line.startswith(b'#!'): - if not is_executable: - # Shebang on a non-executable file - self.files_with_issues[filepath] = None - elif not self.is_valid_shebang(first_line, filepath): - self.files_with_issues[filepath] = [1] - elif is_executable: - # Executable without a shebang - self.files_with_issues[filepath] = None - - -class EndOfFileNewlineIssueTracker(FileIssueTracker): - """Track files that end with an incomplete line - (no newline character at the end of the last line).""" - - heading = "Missing newline at end of file:" - - path_exemptions = BINARY_FILE_PATH_RE - - def check_file_for_issue(self, filepath): - with open(filepath, "rb") as f: - try: - f.seek(-1, 2) - except OSError: - # This script only works on regular files. If we can't seek - # 1 before the end, it means that this position is before - # the beginning of the file, i.e. that the file is empty. - return - if f.read(1) != b"\n": - self.files_with_issues[filepath] = None - - -class Utf8BomIssueTracker(FileIssueTracker): - """Track files that start with a UTF-8 BOM. - Files should be ASCII or UTF-8. Valid UTF-8 does not start with a BOM.""" - - heading = "UTF-8 BOM present:" - - suffix_exemptions = frozenset([".vcxproj", ".sln"]) - path_exemptions = BINARY_FILE_PATH_RE - - def check_file_for_issue(self, filepath): - with open(filepath, "rb") as f: - if f.read().startswith(codecs.BOM_UTF8): - self.files_with_issues[filepath] = None - - -class UnicodeIssueTracker(LineIssueTracker): - """Track lines with invalid characters or invalid text encoding.""" - - heading = "Invalid UTF-8 or forbidden character:" - - # Only allow valid UTF-8, and only other explicitly allowed characters. - # We deliberately exclude all characters that aren't a simple non-blank, - # non-zero-width glyph, apart from a very small set (tab, ordinary space, - # line breaks, "basic" no-break space and soft hyphen). In particular, - # non-ASCII control characters, combinig characters, and Unicode state - # changes (e.g. right-to-left text) are forbidden. - # Note that we do allow some characters with a risk of visual confusion, - # for example '-' (U+002D HYPHEN-MINUS) vs '' (U+00AD SOFT HYPHEN) vs - # '‐' (U+2010 HYPHEN), or 'A' (U+0041 LATIN CAPITAL LETTER A) vs - # 'Α' (U+0391 GREEK CAPITAL LETTER ALPHA). - GOOD_CHARACTERS = ''.join([ - '\t\n\r -~', # ASCII (tabs and line endings are checked separately) - '\u00A0-\u00FF', # Latin-1 Supplement (for NO-BREAK SPACE and punctuation) - '\u2010-\u2027\u2030-\u205E', # General Punctuation (printable) - '\u2070\u2071\u2074-\u208E\u2090-\u209C', # Superscripts and Subscripts - '\u2190-\u21FF', # Arrows - '\u2200-\u22FF', # Mathematical Symbols - '\u2500-\u257F' # Box Drawings characters used in markdown trees - ]) - # Allow any of the characters and ranges above, and anything classified - # as a word constituent. - GOOD_CHARACTERS_RE = re.compile(r'[\w{}]+\Z'.format(GOOD_CHARACTERS)) - - def issue_with_line(self, line, _filepath, line_number): - try: - text = line.decode('utf-8') - except UnicodeDecodeError: - return True - if line_number == 1 and text.startswith('\uFEFF'): - # Strip BOM (U+FEFF ZERO WIDTH NO-BREAK SPACE) at the beginning. - # Which files are allowed to have a BOM is handled in - # Utf8BomIssueTracker. - text = text[1:] - return not self.GOOD_CHARACTERS_RE.match(text) - -class UnixLineEndingIssueTracker(LineIssueTracker): - """Track files with non-Unix line endings (i.e. files with CR).""" - - heading = "Non-Unix line endings:" - - def should_check_file(self, filepath): - if not super().should_check_file(filepath): - return False - return not is_windows_file(filepath) - - def issue_with_line(self, line, _filepath, _line_number): - return b"\r" in line - - -class WindowsLineEndingIssueTracker(LineIssueTracker): - """Track files with non-Windows line endings (i.e. CR or LF not in CRLF).""" - - heading = "Non-Windows line endings:" - - def should_check_file(self, filepath): - if not super().should_check_file(filepath): - return False - return is_windows_file(filepath) - - def issue_with_line(self, line, _filepath, _line_number): - return not line.endswith(b"\r\n") or b"\r" in line[:-2] - - -class TrailingWhitespaceIssueTracker(LineIssueTracker): - """Track lines with trailing whitespace.""" - - heading = "Trailing whitespace:" - suffix_exemptions = frozenset([".dsp", ".md"]) - - def issue_with_line(self, line, _filepath, _line_number): - return line.rstrip(b"\r\n") != line.rstrip() - - -class TabIssueTracker(LineIssueTracker): - """Track lines with tabs.""" - - heading = "Tabs present:" - suffix_exemptions = frozenset([ - ".make", - ".pem", # some openssl dumps have tabs - ".sln", - "/.gitmodules", - "/Makefile", - "/Makefile.inc", - "/generate_visualc_files.pl", - ]) - - def issue_with_line(self, line, _filepath, _line_number): - return b"\t" in line - - -class MergeArtifactIssueTracker(LineIssueTracker): - """Track lines with merge artifacts. - These are leftovers from a ``git merge`` that wasn't fully edited.""" - - heading = "Merge artifact:" - - def issue_with_line(self, line, _filepath, _line_number): - # Detect leftover git conflict markers. - if line.startswith(b'<<<<<<< ') or line.startswith(b'>>>>>>> '): - return True - if line.startswith(b'||||||| '): # from merge.conflictStyle=diff3 - return True - if line.rstrip(b'\r\n') == b'=======' and \ - not _filepath.endswith('.md'): - return True - return False - - -def this_location(): - frame = inspect.currentframe() - assert frame is not None - info = inspect.getframeinfo(frame) - return os.path.basename(info.filename), info.lineno -THIS_FILE_BASE_NAME, LINE_NUMBER_BEFORE_LICENSE_ISSUE_TRACKER = this_location() - -class LicenseIssueTracker(LineIssueTracker): - """Check copyright statements and license indications. - - This class only checks that statements are correct if present. It does - not enforce the presence of statements in each file. - """ - - heading = "License issue:" - - LICENSE_EXEMPTION_RE_LIST = [ - # Third-party code, other than whitelisted third-party modules, - # may be under a different license. - r'3rdparty/(?!(p256-m)/.*)', - # Documentation explaining the license may have accidental - # false positives. - r'(ChangeLog|LICENSE|[-0-9A-Z_a-z]+\.md)\Z', - # Files imported from TF-M, and not used except in test builds, - # may be under a different license. - r'configs/ext/crypto_config_profile_medium\.h\Z', - r'configs/ext/tfm_mbedcrypto_config_profile_medium\.h\Z', - r'configs/ext/README\.md\Z', - # Third-party file. - r'dco\.txt\Z', - ] - path_exemptions = re.compile('|'.join(BINARY_FILE_PATH_RE_LIST + - LICENSE_EXEMPTION_RE_LIST)) - - COPYRIGHT_HOLDER = rb'The Mbed TLS Contributors' - # Catch "Copyright foo", "Copyright (C) foo", "Copyright © foo", etc. - COPYRIGHT_RE = re.compile(rb'.*\bcopyright\s+((?:\w|\s|[()]|[^ -~])*\w)', re.I) - - SPDX_HEADER_KEY = b'SPDX-License-Identifier' - LICENSE_IDENTIFIER = b'Apache-2.0 OR GPL-2.0-or-later' - SPDX_RE = re.compile(br'.*?(' + - re.escape(SPDX_HEADER_KEY) + - br')(:\s*(.*?)\W*\Z|.*)', re.I) - - LICENSE_MENTION_RE = re.compile(rb'.*(?:' + rb'|'.join([ - rb'Apache License', - rb'General Public License', - ]) + rb')', re.I) - - def __init__(self): - super().__init__() - # Record what problem was caused. We can't easily report it due to - # the structure of the script. To be fixed after - # https://github.com/Mbed-TLS/mbedtls/pull/2506 - self.problem = None - - def issue_with_line(self, line, filepath, line_number): - #pylint: disable=too-many-return-statements - - # Use endswith() rather than the more correct os.path.basename() - # because experimentally, it makes a significant difference to - # the running time. - if filepath.endswith(THIS_FILE_BASE_NAME) and \ - line_number > LINE_NUMBER_BEFORE_LICENSE_ISSUE_TRACKER: - # Avoid false positives from the code in this class. - # Also skip the rest of this file, which is highly unlikely to - # contain any problematic statements since we put those near the - # top of files. - return False - - m = self.COPYRIGHT_RE.match(line) - if m and m.group(1) != self.COPYRIGHT_HOLDER: - self.problem = 'Invalid copyright line' - return True - - m = self.SPDX_RE.match(line) - if m: - if m.group(1) != self.SPDX_HEADER_KEY: - self.problem = 'Misspelled ' + self.SPDX_HEADER_KEY.decode() - return True - if not m.group(3): - self.problem = 'Improperly formatted SPDX license identifier' - return True - if m.group(3) != self.LICENSE_IDENTIFIER: - self.problem = 'Wrong SPDX license identifier' - return True - - m = self.LICENSE_MENTION_RE.match(line) - if m: - self.problem = 'Suspicious license mention' - return True - - return False - - -class IntegrityChecker: - """Sanity-check files under the current directory.""" - - def __init__(self, log_file): - """Instantiate the sanity checker. - Check files under the current directory. - Write a report of issues to log_file.""" - build_tree.check_repo_path() - self.logger = None - self.setup_logger(log_file) - self.issues_to_check = [ - ShebangIssueTracker(), - EndOfFileNewlineIssueTracker(), - Utf8BomIssueTracker(), - UnicodeIssueTracker(), - UnixLineEndingIssueTracker(), - WindowsLineEndingIssueTracker(), - TrailingWhitespaceIssueTracker(), - TabIssueTracker(), - MergeArtifactIssueTracker(), - LicenseIssueTracker(), - ] - - def setup_logger(self, log_file, level=logging.INFO): - """Log to log_file if provided, or to stderr if None.""" - self.logger = logging.getLogger() - self.logger.setLevel(level) - if log_file: - handler = logging.FileHandler(log_file) - self.logger.addHandler(handler) - else: - console = logging.StreamHandler() - self.logger.addHandler(console) - - @staticmethod - def collect_files(): - """Return the list of files to check. - - These are the regular files commited into Git. - """ - bytes_output = subprocess.check_output(['git', 'ls-files', '-z']) - bytes_filepaths = bytes_output.split(b'\0')[:-1] - ascii_filepaths = map(lambda fp: fp.decode('ascii'), bytes_filepaths) - # Filter out directories. Normally Git doesn't list directories - # (it only knows about the files inside them), but there is - # at least one case where 'git ls-files' includes a directory: - # submodules. Just skip submodules (and any other directories). - ascii_filepaths = [fp for fp in ascii_filepaths - if os.path.isfile(fp)] - # Prepend './' to files in the top-level directory so that - # something like `'/Makefile' in fp` matches in the top-level - # directory as well as in subdirectories. - return [fp if os.path.dirname(fp) else os.path.join(os.curdir, fp) - for fp in ascii_filepaths] - - def check_files(self): - """Check all files for all issues.""" - for issue_to_check in self.issues_to_check: - for filepath in self.collect_files(): - if issue_to_check.should_check_file(filepath): - issue_to_check.check_file_for_issue(filepath) - - def output_issues(self): - """Log the issues found and their locations. - - Return 1 if there were issues, 0 otherwise. - """ - integrity_return_code = 0 - for issue_to_check in self.issues_to_check: - if issue_to_check.files_with_issues: - integrity_return_code = 1 - issue_to_check.output_file_issues(self.logger) - return integrity_return_code - - -def run_main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument( - "-l", "--log_file", type=str, help="path to optional output log", - ) - check_args = parser.parse_args() - integrity_check = IntegrityChecker(check_args.log_file) - integrity_check.check_files() - return_code = integrity_check.output_issues() - sys.exit(return_code) - - -if __name__ == "__main__": - run_main() -- 2.43.0

From: Simon Glass <sjg@chromium.org> The new Kbuild changes have broken this script, so 'make qcheck' and 'make pcheck' don't work anymore. Unset some environment variables to resolve this. Signed-off-by: Simon Glass <sjg@chromium.org> Fixes: bf030467d24 (Merge patch series "Update kbuild") --- test/run | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/run b/test/run index 768b22577c4..fc1d27004ac 100755 --- a/test/run +++ b/test/run @@ -13,6 +13,9 @@ run_test() { [ $? -ne 0 ] && failures=$((failures+1)) } +# Clean up things the Makefile created +unset MAKE MAKEFLAGS MAKELEVEL MAKEOVERRIDES MAKE_TERMERR MAKE_TERMOUT + # Select test attributes ut_mark_expr=test_ut if [ "$1" = "quick" ]; then -- 2.43.0

From: Simon Glass <sjg@chromium.org> This uclass ID should be up with the other emulators, so move it. Signed-off-by: Simon Glass <sjg@chromium.org> --- include/dm/uclass-id.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h index 281abe99acf..49f98cd2e1a 100644 --- a/include/dm/uclass-id.h +++ b/include/dm/uclass-id.h @@ -34,6 +34,7 @@ enum uclass_id { UCLASS_PCI_EMUL_PARENT, /* parent for PCI device emulators */ UCLASS_USB_EMUL, /* sandbox USB bus device emulator */ UCLASS_AXI_EMUL, /* sandbox AXI bus device emulator */ + UCLASS_FFA_EMUL, /* sandbox FF-A device emulator */ /* U-Boot uclasses start here - in alphabetical order */ UCLASS_ACPI_PMC, /* (x86) Power-management controller (PMC) */ @@ -63,7 +64,6 @@ enum uclass_id { UCLASS_ETH_PHY, /* Ethernet PHY device */ UCLASS_EXTCON, /* External Connector Class */ UCLASS_FFA, /* Arm Firmware Framework for Armv8-A */ - UCLASS_FFA_EMUL, /* sandbox FF-A device emulator */ UCLASS_FIRMWARE, /* Firmware */ UCLASS_FPGA, /* FPGA device */ UCLASS_FUZZING_ENGINE, /* Fuzzing engine */ -- 2.43.0

From: Simon Glass <sjg@chromium.org> Sandbox needs to include system headers in some files, but also wants to use alist. Adjust the headers to permit this. Signed-off-by: Simon Glass <sjg@chromium.org> --- include/alist.h | 16 +++++++++++----- lib/alist.c | 1 + 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/include/alist.h b/include/alist.h index b00d9ea97d6..69d7cdb722f 100644 --- a/include/alist.h +++ b/include/alist.h @@ -10,8 +10,14 @@ #define __ALIST_H #include <stdbool.h> -#include <linux/bitops.h> + +#ifdef USE_HOSTCC +#include <sys/types.h> +#include <stdint.h> +#else #include <linux/types.h> +#endif +#define BIT(nr) (1UL << (nr)) /** * struct alist - object list that can be allocated and freed @@ -41,10 +47,10 @@ */ struct alist { void *data; - u16 obj_size; - u16 count; - u16 alloc; - u16 flags; + unsigned short obj_size; + unsigned short count; + unsigned short alloc; + unsigned short flags; }; /** diff --git a/lib/alist.c b/lib/alist.c index 4ce651f5c45..0ae07f0f5c0 100644 --- a/lib/alist.c +++ b/lib/alist.c @@ -8,6 +8,7 @@ #include <alist.h> #include <display_options.h> +#include <log.h> #include <malloc.h> #include <stdio.h> #include <string.h> -- 2.43.0

From: Simon Glass <sjg@chromium.org> Add a new mem.c file and move memory-related functions to it. Signed-off-by: Simon Glass <sjg@chromium.org> --- arch/sandbox/cpu/Makefile | 2 +- arch/sandbox/cpu/cpu.c | 274 +----------------------------------- arch/sandbox/cpu/mem.c | 288 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 290 insertions(+), 274 deletions(-) create mode 100644 arch/sandbox/cpu/mem.c diff --git a/arch/sandbox/cpu/Makefile b/arch/sandbox/cpu/Makefile index bfcdc335d32..aac65e48675 100644 --- a/arch/sandbox/cpu/Makefile +++ b/arch/sandbox/cpu/Makefile @@ -5,7 +5,7 @@ # (C) Copyright 2000-2003 # Wolfgang Denk, DENX Software Engineering, wd@denx.de. -obj-y := cache.o cpu.o state.o +obj-y := cache.o cpu.o mem.o state.o extra-y := start.o os.o extra-$(CONFIG_SANDBOX_SDL) += sdl.o obj-$(CONFIG_XPL_BUILD) += spl.o diff --git a/arch/sandbox/cpu/cpu.c b/arch/sandbox/cpu/cpu.c index 6db8739e66b..218057b94ad 100644 --- a/arch/sandbox/cpu/cpu.c +++ b/arch/sandbox/cpu/cpu.c @@ -9,27 +9,16 @@ #include <cpu_func.h> #include <errno.h> #include <log.h> +#include <mapmem.h> #include <os.h> -#include <setjmp.h> #include <asm/global_data.h> -#include <asm/io.h> #include <asm/malloc.h> #include <asm/state.h> -#include <dm/ofnode.h> #include <linux/delay.h> #include <linux/libfdt.h> DECLARE_GLOBAL_DATA_PTR; -/* Enable access to PCI memory with map_sysmem() */ -static bool enable_pci_map; - -#ifdef CONFIG_PCI -/* Last device that was mapped into memory, and length of mapping */ -static struct udevice *map_dev; -unsigned long map_len; -#endif - void __noreturn sandbox_exit(void) { /* Do this here while it still has an effect */ @@ -61,267 +50,6 @@ int cleanup_before_linux_select(int flags) return 0; } -/** - * is_in_sandbox_mem() - Checks if a pointer is within sandbox's emulated DRAM - * - * This provides a way to check if a pointer is owned by sandbox (and is within - * its RAM) or not. Sometimes pointers come from a test which conceptually runs - * output sandbox, potentially with direct access to the C-library malloc() - * function, or the sandbox stack (which is not actually within the emulated - * DRAM. - * - * Such pointers obviously cannot be mapped into sandbox's DRAM, so we must - * detect them an process them separately, by recording a mapping to a tag, - * which we can use to map back to the pointer later. - * - * @ptr: Pointer to check - * Return: true if this is within sandbox emulated DRAM, false if not - */ -static bool is_in_sandbox_mem(const void *ptr) -{ - return (const uint8_t *)ptr >= gd->arch.ram_buf && - (const uint8_t *)ptr < gd->arch.ram_buf + gd->ram_size; -} - -/** - * phys_to_virt() - Converts a sandbox RAM address to a pointer - * - * Sandbox uses U-Boot addresses from 0 to the size of DRAM. These index into - * the emulated DRAM buffer used by sandbox. This function converts such an - * address to a pointer into this buffer, which can be used to access the - * memory. - * - * If the address is outside this range, it is assumed to be a tag - */ -void *phys_to_virt(phys_addr_t paddr) -{ - struct sandbox_mapmem_entry *mentry; - struct sandbox_state *state; - - /* If the address is within emulated DRAM, calculate the value */ - if (paddr < gd->ram_size) - return (void *)(gd->arch.ram_buf + paddr); - - /* - * Otherwise search out list of tags for the correct pointer previously - * created by map_to_sysmem() - */ - state = state_get_current(); - list_for_each_entry(mentry, &state->mapmem_head, sibling_node) { - if (mentry->tag == paddr) { - log_debug("Used map from %lx to %p\n", (ulong)paddr, - mentry->ptr); - mentry->refcnt++; - return mentry->ptr; - } - } - - printf("%s: Cannot map sandbox address %lx (SDRAM from 0 to %lx)\n", - __func__, (ulong)paddr, (ulong)gd->ram_size); - os_abort(); - - /* Not reached */ - return NULL; -} - -struct sandbox_mapmem_entry *find_tag(const void *ptr) -{ - struct sandbox_mapmem_entry *mentry; - struct sandbox_state *state = state_get_current(); - - list_for_each_entry(mentry, &state->mapmem_head, sibling_node) { - if (mentry->ptr == ptr) { - log_debug("Used map from %p to %lx\n", ptr, - mentry->tag); - return mentry; - } - } - - return NULL; -} - -phys_addr_t virt_to_phys(void *ptr) -{ - struct sandbox_mapmem_entry *mentry; - - /* - * If it is in emulated RAM, don't bother looking for a tag. Just - * calculate the pointer using the provides offset into the RAM buffer. - */ - if (is_in_sandbox_mem(ptr)) - return (phys_addr_t)((uint8_t *)ptr - gd->arch.ram_buf); - - mentry = find_tag(ptr); - if (!mentry) { - /* Abort so that gdb can be used here */ - printf("%s: Cannot map sandbox address %p (SDRAM from 0 to %lx)\n", - __func__, ptr, (ulong)gd->ram_size); - os_abort(); - } - log_debug("Used map from %p to %lx\n", ptr, mentry->tag); - - return mentry->tag; -} - -void *map_physmem(phys_addr_t paddr, unsigned long len, unsigned long flags) -{ -#if defined(CONFIG_PCI) && !defined(CONFIG_XPL_BUILD) - unsigned long plen = len; - void *ptr; - - map_dev = NULL; - if (enable_pci_map && !pci_map_physmem(paddr, &len, &map_dev, &ptr)) { - if (plen != len) { - printf("%s: Warning: partial map at %x, wanted %lx, got %lx\n", - __func__, (uint)paddr, len, plen); - } - map_len = len; - log_debug("pci map %lx -> %p\n", (ulong)paddr, ptr); - return ptr; - } -#endif - - return phys_to_virt(paddr); -} - -void unmap_physmem(const void *ptr, unsigned long flags) -{ - struct sandbox_mapmem_entry *mentry; - -#ifdef CONFIG_PCI - if (map_dev) { - pci_unmap_physmem(ptr, map_len, map_dev); - map_dev = NULL; - } -#endif - - /* If it is in emulated RAM, we didn't create a tag, so nothing to do */ - if (is_in_sandbox_mem(ptr)) - return; - - mentry = find_tag(ptr); - if (mentry) { - if (!--mentry->refcnt) { - list_del(&mentry->sibling_node); - log_debug("Removed map from %p to %lx\n", ptr, - (ulong)mentry->tag); - free(mentry); - } - } else { - log_warning("Address not mapped: %p\n", ptr); - } -} - -phys_addr_t map_to_sysmem(const void *ptr) -{ - struct sandbox_mapmem_entry *mentry; - - /* - * If it is in emulated RAM, don't bother creating a tag. Just return - * the offset into the RAM buffer. - */ - if (is_in_sandbox_mem(ptr)) - return (u8 *)ptr - gd->arch.ram_buf; - - /* - * See if there is an existing tag with this pointer. If not, set up a - * new one. - */ - mentry = find_tag(ptr); - if (!mentry) { - struct sandbox_state *state = state_get_current(); - - mentry = malloc(sizeof(*mentry)); - if (!mentry) { - printf("%s: Error: Out of memory\n", __func__); - os_exit(ENOMEM); - } - mentry->tag = state->next_tag++; - mentry->ptr = (void *)ptr; - mentry->refcnt = 0; - list_add_tail(&mentry->sibling_node, &state->mapmem_head); - log_debug("Added map from %p to %lx\n", ptr, - (ulong)mentry->tag); - } - - mentry->refcnt++; - - /* - * Return the tag as the address to use. A later call to map_sysmem() - * will return ptr - */ - return mentry->tag; -} - -void sandbox_map_list(void) -{ - struct sandbox_mapmem_entry *mentry; - struct sandbox_state *state = state_get_current(); - - printf("Sandbox memory-mapping\n"); - printf("%8s %16s %6s\n", "Addr", "Mapping", "Refcnt"); - list_for_each_entry(mentry, &state->mapmem_head, sibling_node) { - printf("%8lx %p %6d\n", mentry->tag, mentry->ptr, - mentry->refcnt); - } -} - -unsigned long sandbox_read(const void *addr, enum sandboxio_size_t size) -{ - struct sandbox_state *state = state_get_current(); - - if (!state->allow_memio) - return 0; - - switch (size) { - case SB_SIZE_8: - return *(u8 *)addr; - case SB_SIZE_16: - return *(u16 *)addr; - case SB_SIZE_32: - return *(u32 *)addr; - case SB_SIZE_64: - return *(u64 *)addr; - } - - return 0; -} - -void sandbox_write(void *addr, unsigned int val, enum sandboxio_size_t size) -{ - struct sandbox_state *state = state_get_current(); - - if (!state->allow_memio) - return; - - switch (size) { - case SB_SIZE_8: - *(u8 *)addr = val; - break; - case SB_SIZE_16: - *(u16 *)addr = val; - break; - case SB_SIZE_32: - *(u32 *)addr = val; - break; - case SB_SIZE_64: - *(u64 *)addr = val; - break; - } -} - -void sandbox_set_enable_memio(bool enable) -{ - struct sandbox_state *state = state_get_current(); - - state->allow_memio = enable; -} - -void sandbox_set_enable_pci_map(int enable) -{ - enable_pci_map = enable; -} - void dcache_enable(void) { } diff --git a/arch/sandbox/cpu/mem.c b/arch/sandbox/cpu/mem.c new file mode 100644 index 00000000000..54a55c1558c --- /dev/null +++ b/arch/sandbox/cpu/mem.c @@ -0,0 +1,288 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2011 The Chromium OS Authors. + * Copyright 2025 Simon Glass <sjg@chromium.org> + */ + +#define LOG_CATEGORY LOGC_SANDBOX + +#include <errno.h> +#include <log.h> +#include <malloc.h> +#include <os.h> +#include <asm/global_data.h> +#include <asm/io.h> +#include <asm/state.h> +#include <linux/list.h> + +DECLARE_GLOBAL_DATA_PTR; + +/* Enable access to PCI memory with map_sysmem() */ +static bool enable_pci_map; + +#ifdef CONFIG_PCI +/* Last device that was mapped into memory, and length of mapping */ +static struct udevice *map_dev; +unsigned long map_len; +#endif + +/** + * is_in_sandbox_mem() - Checks if a pointer is within sandbox's emulated DRAM + * + * This provides a way to check if a pointer is owned by sandbox (and is within + * its RAM) or not. Sometimes pointers come from a test which conceptually runs + * output sandbox, potentially with direct access to the C-library malloc() + * function, or the sandbox stack (which is not actually within the emulated + * DRAM. + * + * Such pointers obviously cannot be mapped into sandbox's DRAM, so we must + * detect them an process them separately, by recording a mapping to a tag, + * which we can use to map back to the pointer later. + * + * @ptr: Pointer to check + * Return: true if this is within sandbox emulated DRAM, false if not + */ +static bool is_in_sandbox_mem(const void *ptr) +{ + return (const uint8_t *)ptr >= gd->arch.ram_buf && + (const uint8_t *)ptr < gd->arch.ram_buf + gd->ram_size; +} + +/** + * phys_to_virt() - Converts a sandbox RAM address to a pointer + * + * Sandbox uses U-Boot addresses from 0 to the size of DRAM. These index into + * the emulated DRAM buffer used by sandbox. This function converts such an + * address to a pointer into this buffer, which can be used to access the + * memory. + * + * If the address is outside this range, it is assumed to be a tag + */ +void *phys_to_virt(phys_addr_t paddr) +{ + struct sandbox_mapmem_entry *mentry; + struct sandbox_state *state; + + /* If the address is within emulated DRAM, calculate the value */ + if (paddr < gd->ram_size) + return (void *)(gd->arch.ram_buf + paddr); + + /* + * Otherwise search out list of tags for the correct pointer previously + * created by map_to_sysmem() + */ + state = state_get_current(); + list_for_each_entry(mentry, &state->mapmem_head, sibling_node) { + if (mentry->tag == paddr) { + log_debug("Used map from %lx to %p\n", (ulong)paddr, + mentry->ptr); + mentry->refcnt++; + return mentry->ptr; + } + } + + printf("%s: Cannot map sandbox address %lx (SDRAM from 0 to %lx)\n", + __func__, (ulong)paddr, (ulong)gd->ram_size); + os_abort(); + + /* Not reached */ + return NULL; +} + +struct sandbox_mapmem_entry *find_tag(const void *ptr) +{ + struct sandbox_mapmem_entry *mentry; + struct sandbox_state *state = state_get_current(); + + list_for_each_entry(mentry, &state->mapmem_head, sibling_node) { + if (mentry->ptr == ptr) { + log_debug("Used map from %p to %lx\n", ptr, + mentry->tag); + return mentry; + } + } + + return NULL; +} + +phys_addr_t virt_to_phys(void *ptr) +{ + struct sandbox_mapmem_entry *mentry; + + /* + * If it is in emulated RAM, don't bother looking for a tag. Just + * calculate the pointer using the provides offset into the RAM buffer. + */ + if (is_in_sandbox_mem(ptr)) + return (phys_addr_t)((uint8_t *)ptr - gd->arch.ram_buf); + + mentry = find_tag(ptr); + if (!mentry) { + /* Abort so that gdb can be used here */ + printf("%s: Cannot map sandbox address %p (SDRAM from 0 to %lx)\n", + __func__, ptr, (ulong)gd->ram_size); + os_abort(); + } + log_debug("Used map from %p to %lx\n", ptr, mentry->tag); + + return mentry->tag; +} + +void *map_physmem(phys_addr_t paddr, unsigned long len, unsigned long flags) +{ +#if defined(CONFIG_PCI) && !defined(CONFIG_XPL_BUILD) + unsigned long plen = len; + void *ptr; + + map_dev = NULL; + if (enable_pci_map && !pci_map_physmem(paddr, &len, &map_dev, &ptr)) { + if (plen != len) { + printf("%s: Warning: partial map at %x, wanted %lx, got %lx\n", + __func__, (uint)paddr, len, plen); + } + map_len = len; + log_debug("pci map %lx -> %p\n", (ulong)paddr, ptr); + return ptr; + } +#endif + + return phys_to_virt(paddr); +} + +void unmap_physmem(const void *ptr, unsigned long flags) +{ + struct sandbox_mapmem_entry *mentry; + +#ifdef CONFIG_PCI + if (map_dev) { + pci_unmap_physmem(ptr, map_len, map_dev); + map_dev = NULL; + } +#endif + + /* If it is in emulated RAM, we didn't create a tag, so nothing to do */ + if (is_in_sandbox_mem(ptr)) + return; + + mentry = find_tag(ptr); + if (mentry) { + if (!--mentry->refcnt) { + list_del(&mentry->sibling_node); + log_debug("Removed map from %p to %lx\n", ptr, + (ulong)mentry->tag); + free(mentry); + } + } else { + log_warning("Address not mapped: %p\n", ptr); + } +} + +phys_addr_t map_to_sysmem(const void *ptr) +{ + struct sandbox_mapmem_entry *mentry; + + /* + * If it is in emulated RAM, don't bother creating a tag. Just return + * the offset into the RAM buffer. + */ + if (is_in_sandbox_mem(ptr)) + return (u8 *)ptr - gd->arch.ram_buf; + + /* + * See if there is an existing tag with this pointer. If not, set up a + * new one. + */ + mentry = find_tag(ptr); + if (!mentry) { + struct sandbox_state *state = state_get_current(); + + mentry = malloc(sizeof(*mentry)); + if (!mentry) { + printf("%s: Error: Out of memory\n", __func__); + os_exit(ENOMEM); + } + mentry->tag = state->next_tag++; + mentry->ptr = (void *)ptr; + mentry->refcnt = 0; + list_add_tail(&mentry->sibling_node, &state->mapmem_head); + log_debug("Added map from %p to %lx\n", ptr, + (ulong)mentry->tag); + } + + mentry->refcnt++; + + /* + * Return the tag as the address to use. A later call to map_sysmem() + * will return ptr + */ + return mentry->tag; +} + +void sandbox_map_list(void) +{ + struct sandbox_mapmem_entry *mentry; + struct sandbox_state *state = state_get_current(); + + printf("Sandbox memory-mapping\n"); + printf("%8s %16s %6s\n", "Addr", "Mapping", "Refcnt"); + list_for_each_entry(mentry, &state->mapmem_head, sibling_node) { + printf("%8lx %p %6d\n", mentry->tag, mentry->ptr, + mentry->refcnt); + } +} + +unsigned long sandbox_read(const void *addr, enum sandboxio_size_t size) +{ + struct sandbox_state *state = state_get_current(); + + if (!state->allow_memio) + return 0; + + switch (size) { + case SB_SIZE_8: + return *(u8 *)addr; + case SB_SIZE_16: + return *(u16 *)addr; + case SB_SIZE_32: + return *(u32 *)addr; + case SB_SIZE_64: + return *(u64 *)addr; + } + + return 0; +} + +void sandbox_write(void *addr, unsigned int val, enum sandboxio_size_t size) +{ + struct sandbox_state *state = state_get_current(); + + if (!state->allow_memio) + return; + + switch (size) { + case SB_SIZE_8: + *(u8 *)addr = val; + break; + case SB_SIZE_16: + *(u16 *)addr = val; + break; + case SB_SIZE_32: + *(u32 *)addr = val; + break; + case SB_SIZE_64: + *(u64 *)addr = val; + break; + } +} + +void sandbox_set_enable_memio(bool enable) +{ + struct sandbox_state *state = state_get_current(); + + state->allow_memio = enable; +} + +void sandbox_set_enable_pci_map(int enable) +{ + enable_pci_map = enable; +} -- 2.43.0

From: Simon Glass <sjg@chromium.org> Add a way for calls to readl()/writel() etc. to be picked up by a driver in order to implement MMIO. This works by registering some functions, which are then called when accesses occur. Add comments to sandbox_read() and sandbox_write() while we are here. Signed-off-by: Simon Glass <sjg@chromium.org> --- arch/sandbox/cpu/mem.c | 49 +++++++++++++++++++++++ arch/sandbox/cpu/state.c | 2 + arch/sandbox/include/asm/io.h | 15 +++++++ arch/sandbox/include/asm/state.h | 67 ++++++++++++++++++++++++++++++++ 4 files changed, 133 insertions(+) diff --git a/arch/sandbox/cpu/mem.c b/arch/sandbox/cpu/mem.c index 54a55c1558c..010dc9c16a0 100644 --- a/arch/sandbox/cpu/mem.c +++ b/arch/sandbox/cpu/mem.c @@ -6,6 +6,7 @@ #define LOG_CATEGORY LOGC_SANDBOX +#include <alist.h> #include <errno.h> #include <log.h> #include <malloc.h> @@ -231,9 +232,20 @@ void sandbox_map_list(void) } } +static bool in_range(const struct sandbox_mmio *mmio, const void *addr) +{ + return addr >= mmio->base && addr < mmio->base + mmio->size; +} + unsigned long sandbox_read(const void *addr, enum sandboxio_size_t size) { struct sandbox_state *state = state_get_current(); + const struct sandbox_mmio *mmio; + + alist_for_each(mmio, &state->mmio) { + if (in_range(mmio, addr)) + return mmio->h_read(mmio->ctx, addr, size); + } if (!state->allow_memio) return 0; @@ -255,6 +267,14 @@ unsigned long sandbox_read(const void *addr, enum sandboxio_size_t size) void sandbox_write(void *addr, unsigned int val, enum sandboxio_size_t size) { struct sandbox_state *state = state_get_current(); + const struct sandbox_mmio *mmio; + + alist_for_each(mmio, &state->mmio) { + if (in_range(mmio, addr)) { + mmio->h_write(mmio->ctx, addr, val, size); + return; + } + } if (!state->allow_memio) return; @@ -286,3 +306,32 @@ void sandbox_set_enable_pci_map(int enable) { enable_pci_map = enable; } + +int sandbox_mmio_add(void *base, ulong size, sandbox_mmio_read_func h_read, + sandbox_mmio_write_func h_write, void *ctx) +{ + struct sandbox_state *state = state_get_current(); + struct sandbox_mmio mmio; + + mmio.base = base; + mmio.size = size; + mmio.h_read = h_read; + mmio.h_write = h_write; + mmio.ctx = ctx; + if (!alist_add(&state->mmio, mmio)) + return -ENOMEM; + + return 0; +} + +void sandbox_mmio_remove(void *ctx) +{ + struct sandbox_state *state = state_get_current(); + struct sandbox_mmio *from, *to; + + alist_for_each_filter(from, to, &state->mmio) { + if (from->ctx != ctx) + *to++ = *from; + } + alist_update_end(&state->mmio, to); +} diff --git a/arch/sandbox/cpu/state.c b/arch/sandbox/cpu/state.c index 49236db99c2..949ca42de94 100644 --- a/arch/sandbox/cpu/state.c +++ b/arch/sandbox/cpu/state.c @@ -3,6 +3,7 @@ * Copyright (c) 2011-2012 The Chromium OS Authors. */ +#include <alist.h> #include <bloblist.h> #include <config.h> #include <errno.h> @@ -485,6 +486,7 @@ int state_init(void) printf("Out of memory\n"); os_exit(1); } + alist_init_struct(&state->mmio, struct sandbox_mmio); state_reset_for_test(state); /* diff --git a/arch/sandbox/include/asm/io.h b/arch/sandbox/include/asm/io.h index 3c3545a2747..afcdf65ecbb 100644 --- a/arch/sandbox/include/asm/io.h +++ b/arch/sandbox/include/asm/io.h @@ -33,7 +33,22 @@ void unmap_physmem(const void *vaddr, unsigned long flags); /* Map from a pointer to our RAM buffer */ phys_addr_t map_to_sysmem(const void *ptr); +/** + * sandbox_read() - Perform a memory read + * + * @addr: Pointer to read from + * @size: Access size of read + * Return: Value obtained + */ unsigned long sandbox_read(const void *addr, enum sandboxio_size_t size); + +/** + * sandbox_write() - Perform a memory write + * + * @addr: Pointer to write to + * @val: Value to write + * @size: Access size of write + */ void sandbox_write(void *addr, unsigned int val, enum sandboxio_size_t size); #define readb(addr) sandbox_read((const void *)addr, SB_SIZE_8) diff --git a/arch/sandbox/include/asm/state.h b/arch/sandbox/include/asm/state.h index ff7f539965e..3aa35c112be 100644 --- a/arch/sandbox/include/asm/state.h +++ b/arch/sandbox/include/asm/state.h @@ -6,11 +6,14 @@ #ifndef __SANDBOX_STATE_H #define __SANDBOX_STATE_H +#include <alist.h> #include <sysreset.h> #include <stdbool.h> #include <linux/list.h> #include <linux/stringify.h> +enum sandboxio_size_t; + enum { SB_MAX_BINDS = 4, }; @@ -68,6 +71,69 @@ struct sandbox_mapmem_entry { struct list_head sibling_node; }; +/** + * sandbox_read() - Read function for sandbox_mmio + * + * @addr: Pointer to read from + * @size: Access size of read + * Return: Value obtained + */ +typedef long (*sandbox_mmio_read_func)(void *ctx, const void *addr, + enum sandboxio_size_t size); + +/** + * sandbox_write() - Write function for sandbox_mmio + * + * @addr: Pointer to write to + * @val: Value to write + * @size: Access size of write + */ +typedef void (*sandbox_mmio_write_func)(void *ctx, void *addr, unsigned int val, + enum sandboxio_size_t size); + +/** + * sandbox_mmio_add() - Add a new MMIO region + * + * Register a new set of read/write functions to be called for a particular + * memory region + * + * @base: Base pointer for region + * @size: Size of region + * @h_read: Read handler + * @h_write: Write handler + * @ctx: Context pointer to passed to read/write functions + */ +int sandbox_mmio_add(void *base, ulong size, sandbox_mmio_read_func h_read, + sandbox_mmio_write_func h_write, void *ctx); + +/** + * sandbox_mmio_remove() - Remove an MMIO region + * + * All regions with the given @ctx are removed + * + * @ctx: Context to search for + */ +void sandbox_mmio_remove(void *ctx); + +/** + * struct sandbox_mmio - defines a region of memory-mapped I/O + * + * This allows accesses to a region of memory to go through provided functions + * + * @base: Base pointer of region + * @size: Size of region + * @h_read: Read handler + * @h_write: Write handler + * @ctx: Context pointer provided when registering + */ +struct sandbox_mmio { + void *base; + ulong size; + sandbox_mmio_read_func h_read; + sandbox_mmio_write_func h_write; + void *ctx; +}; + /* The complete state of the test system */ struct sandbox_state { const char *cmd; /* Command to execute */ @@ -126,6 +192,7 @@ struct sandbox_state { const char *binds[SB_MAX_BINDS]; /* list of -B arguments */ int num_binds; /* number of -B arguments */ + struct alist mmio; /* list of struct sandbox_mmio */ /* * This struct is getting large. * -- 2.43.0

From: Simon Glass <sjg@chromium.org> These values are used in the devicetree and the C code, so move them to a new dt-bindings header. Signed-off-by: Simon Glass <sjg@chromium.org> --- arch/sandbox/dts/test.dts | 5 +++-- include/dt-bindings/virtio.h | 19 +++++++++++++++++++ include/virtio.h | 5 +---- 3 files changed, 23 insertions(+), 6 deletions(-) create mode 100644 include/dt-bindings/virtio.h diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts index 3e8db5c09c6..d0384e9b7e1 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -15,6 +15,7 @@ #include <dt-bindings/input/input.h> #include <dt-bindings/pinctrl/sandbox-pinmux.h> #include <dt-bindings/mux/mux.h> +#include <dt-bindings/virtio.h> / { model = "sandbox"; @@ -1691,7 +1692,7 @@ sandbox_virtio1 { compatible = "sandbox,virtio1"; - virtio-type = <4>; /* rng */ + virtio-type = <VIRTIO_ID_RNG>; }; sandbox_virtio2 { @@ -1700,7 +1701,7 @@ sandbox-virtio-blk { compatible = "sandbox,virtio1"; - virtio-type = <2>; /* block */ + virtio-type = <VIRTIO_ID_BLOCK>; }; sandbox_scmi { diff --git a/include/dt-bindings/virtio.h b/include/dt-bindings/virtio.h new file mode 100644 index 00000000000..9e3fea14567 --- /dev/null +++ b/include/dt-bindings/virtio.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2018, Tuomas Tynkkynen <tuomas.tynkkynen@iki.fi> + * Copyright (C) 2018, Bin Meng <bmeng.cn@gmail.com> + * + * Binding file for virtio IDs + * + * This file is largely based on Linux kernel virtio_*.h files + */ + +#ifndef __DT_BINDINGS_VIRTIO +#define __DT_BINDINGS_VIRTIO + +#define VIRTIO_ID_NET 1 /* virtio net */ +#define VIRTIO_ID_BLOCK 2 /* virtio block */ +#define VIRTIO_ID_RNG 4 /* virtio rng */ +#define VIRTIO_ID_MAX_NUM 27 + +#endif diff --git a/include/virtio.h b/include/virtio.h index 17f894a79e3..ef4eeb16a29 100644 --- a/include/virtio.h +++ b/include/virtio.h @@ -22,13 +22,10 @@ #include <virtio_types.h> #include <dm/device.h> +#include <dt-bindings/virtio.h> #include <linux/bitops.h> #include <linux/bug.h> #include <linux/typecheck.h> -#define VIRTIO_ID_NET 1 /* virtio net */ -#define VIRTIO_ID_BLOCK 2 /* virtio block */ -#define VIRTIO_ID_RNG 4 /* virtio rng */ -#define VIRTIO_ID_MAX_NUM 5 #define VIRTIO_NET_DRV_NAME "virtio-net" #define VIRTIO_BLK_DRV_NAME "virtio-blk" -- 2.43.0

From: Simon Glass <sjg@chromium.org> Add a header file to allow other drivers to implement MMIO and share the code. Signed-off-by: Simon Glass <sjg@chromium.org> --- drivers/virtio/virtio_internal.h | 19 +++++++++++++++++++ drivers/virtio/virtio_mmio.c | 7 ++++--- 2 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 drivers/virtio/virtio_internal.h diff --git a/drivers/virtio/virtio_internal.h b/drivers/virtio/virtio_internal.h new file mode 100644 index 00000000000..96b6bffbe54 --- /dev/null +++ b/drivers/virtio/virtio_internal.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Internal header file for virtio + * + * Copyright 2025 Simon Glass <sjg@chromium.org> + */ + +#ifndef _VIRTIO_INTERNAL_H +#define _VIRTIO_INTERNAL_H + +struct udevice; + +/* MMIO operations from virtio_mmcio.c */ +extern const struct dm_virtio_ops virtio_mmio_ops; + +/* exported probe function from virtio_mmcio.c */ +int virtio_mmio_probe(struct udevice *udev); + +#endif diff --git a/drivers/virtio/virtio_mmio.c b/drivers/virtio/virtio_mmio.c index 1cd737aca24..190a4d617ad 100644 --- a/drivers/virtio/virtio_mmio.c +++ b/drivers/virtio/virtio_mmio.c @@ -344,13 +344,14 @@ static int virtio_mmio_of_to_plat(struct udevice *udev) return 0; } -static int virtio_mmio_probe(struct udevice *udev) +int virtio_mmio_probe(struct udevice *udev) { struct virtio_mmio_priv *priv = dev_get_priv(udev); struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(udev); u32 magic; /* Check magic value */ + log_debug("probe %p\n", priv->base); magic = readl(priv->base + VIRTIO_MMIO_MAGIC_VALUE); if (magic != ('v' | 'i' << 8 | 'r' << 16 | 't' << 24)) { debug("(%s): wrong magic value 0x%08x!\n", udev->name, magic); @@ -379,13 +380,13 @@ static int virtio_mmio_probe(struct udevice *udev) if (priv->version == 1) writel(PAGE_SIZE, priv->base + VIRTIO_MMIO_GUEST_PAGE_SIZE); - debug("(%s): device (%d) vendor (%08x) version (%d)\n", udev->name, + debug("(%s): device (%04x) vendor (%04x) version (%d)\n", udev->name, uc_priv->device, uc_priv->vendor, priv->version); return 0; } -static const struct dm_virtio_ops virtio_mmio_ops = { +const struct dm_virtio_ops virtio_mmio_ops = { .get_config = virtio_mmio_get_config, .set_config = virtio_mmio_set_config, .generation = virtio_mmio_generation, -- 2.43.0

From: Simon Glass <sjg@chromium.org> The existing sandbox implementation of virtio only tests the basic API. It is not able to provide a block device, for example. Add a new implementation which operations at a higher level. It makes use of the existing MMIO driver to perform virtio operations. This emulator-device should be the parent of a function-specific emulator. That emulator uses this MMIO transport to communicate with the controller: virtio-blk { compatible = "sandbox,virtio-blk-emul"; mmio { compatible = "sandbox,virtio-emul"; }; }; A new UCLASS_VIRTIO_EMUL uclass is created for the child devices, which implement the actual function (block device, random-number generator, etc.) Signed-off-by: Simon Glass <sjg@chromium.org> --- arch/Kconfig | 1 + configs/tools-only_defconfig | 2 +- drivers/virtio/Kconfig | 8 + drivers/virtio/Makefile | 1 + drivers/virtio/sandbox_emul.c | 313 ++++++++++++++++++++++++++++++++++ drivers/virtio/sandbox_emul.h | 110 ++++++++++++ include/dm/uclass-id.h | 1 + 7 files changed, 435 insertions(+), 1 deletion(-) create mode 100644 drivers/virtio/sandbox_emul.c create mode 100644 drivers/virtio/sandbox_emul.h diff --git a/arch/Kconfig b/arch/Kconfig index 493c827d74d..fa7839f6c30 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -220,6 +220,7 @@ config SANDBOX imply VIRTIO_MMIO imply VIRTIO_PCI imply VIRTIO_SANDBOX + imply VIRTIO_SANDBOX_EMUL # Re-enable this when fully implemented # imply VIRTIO_BLK imply VIRTIO_NET diff --git a/configs/tools-only_defconfig b/configs/tools-only_defconfig index c0c5387928f..20ac1e63f09 100644 --- a/configs/tools-only_defconfig +++ b/configs/tools-only_defconfig @@ -9,7 +9,6 @@ CONFIG_PCI=y CONFIG_ANDROID_BOOT_IMAGE=y CONFIG_TIMESTAMP=y CONFIG_FIT=y -CONFIG_FIT_SIGNATURE=y # CONFIG_BOOTSTD_FULL is not set # CONFIG_BOOTMETH_CROS is not set # CONFIG_BOOTMETH_VBE is not set @@ -38,5 +37,6 @@ CONFIG_TIMER=y # CONFIG_VIRTIO_MMIO is not set # CONFIG_VIRTIO_PCI is not set # CONFIG_VIRTIO_SANDBOX is not set +# CONFIG_VIRTIO_SANDBOX_EMUL is not set # CONFIG_GENERATE_ACPI_TABLE is not set CONFIG_TOOLS_MKEFICAPSULE=y diff --git a/drivers/virtio/Kconfig b/drivers/virtio/Kconfig index 512ac376f18..858556fe802 100644 --- a/drivers/virtio/Kconfig +++ b/drivers/virtio/Kconfig @@ -54,6 +54,14 @@ config VIRTIO_SANDBOX This driver provides support for Sandbox implementation of virtio transport driver which is used for testing purpose only. +config VIRTIO_SANDBOX_EMUL + bool "Sandbox MMIO emulator for virtio devices" + depends on SANDBOX + select VIRTIO + help + This driver provides an MMIO interface to an emulation of a block + device. It is used for testing purpose only. + config VIRTIO_NET bool "virtio net driver" depends on VIRTIO && NETDEVICES diff --git a/drivers/virtio/Makefile b/drivers/virtio/Makefile index 4c63a6c6904..d928c7b0ad2 100644 --- a/drivers/virtio/Makefile +++ b/drivers/virtio/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_VIRTIO_MMIO) += virtio_mmio.o obj-$(CONFIG_VIRTIO_PCI) += virtio_pci_modern.o obj-$(CONFIG_VIRTIO_PCI_LEGACY) += virtio_pci_legacy.o obj-$(CONFIG_VIRTIO_SANDBOX) += virtio_sandbox.o +obj-$(CONFIG_VIRTIO_SANDBOX_EMUL) += sandbox_emul.o obj-$(CONFIG_VIRTIO_NET) += virtio_net.o obj-$(CONFIG_VIRTIO_BLK) += virtio_blk.o obj-$(CONFIG_VIRTIO_RNG) += virtio_rng.o diff --git a/drivers/virtio/sandbox_emul.c b/drivers/virtio/sandbox_emul.c new file mode 100644 index 00000000000..2c75fd546d1 --- /dev/null +++ b/drivers/virtio/sandbox_emul.c @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * VirtIO Sandbox emulator, for testing purpose only. This emulates the QEMU + * side of virtio, using the MMIO driver and handling any accesses + * + * This handles traffic from the virtio_ring + * + * Copyright 2025 Simon Glass <sjg@chromium.org> + */ + +#define LOG_CATEGORY UCLASS_VIRTIO + +#include <dm.h> +#include <malloc.h> +#include <virtio.h> +#include <asm/io.h> +#include <dt-bindings/virtio.h> +#include <asm/state.h> +#include <linux/sizes.h> +#include "sandbox_emul.h" +#include "virtio_types.h" +#include "virtio_blk.h" +#include "virtio_internal.h" +#include "virtio_mmio.h" +#include "virtio_ring.h" + +enum { + MMIO_SIZE = 0x200, + VENDOR_ID = 0xf003, + DEVICE_ID = VIRTIO_ID_BLOCK, + DISK_SIZE_MB = 16, +}; + +void process_queue(struct udevice *emul_dev, struct sandbox_emul_priv *priv, + uint32_t queue_idx) +{ + struct virtio_emul_ops *ops = virtio_emul_get_ops(emul_dev); + bool processed_something = false; + struct virtio_emul_queue *q; + struct vring_avail *avail; + struct vring_desc *desc; + struct vring_used *used; + uint old_used_idx; + + if (queue_idx >= priv->num_queues) + return; + log_debug("Notified on queue %u\n", queue_idx); + + q = &priv->queues[queue_idx]; + if (!q->ready) + return; + + desc = (struct vring_desc *)q->desc_addr; + avail = (struct vring_avail *)q->avail_addr; + used = (struct vring_used *)q->used_addr; + old_used_idx = used->idx; + + while (q->last_avail_idx != avail->idx) { + processed_something = true; + uint ring_idx = q->last_avail_idx % q->num; + uint desc_head_idx = avail->ring[ring_idx]; + uint used_ring_idx; + int written; + int ret; + + log_debug("Found request at avail ring index %u (desc head %u)\n", + ring_idx, desc_head_idx); + + ret = ops->process_request(emul_dev, desc, desc_head_idx, + &written); + if (ret) + log_warning("Failed to process request (err=%dE)\n", + ret); + + used_ring_idx = used->idx % q->num; + used->ring[used_ring_idx].id = desc_head_idx; + used->ring[used_ring_idx].len = written; + used->idx++; + q->last_avail_idx++; + } + + if (processed_something) { + bool needs_interrupt = true; + + log_debug("finished processing, new used_idx is %d.\n", + used->idx); + if (priv->driver_features & BIT(VIRTIO_RING_F_EVENT_IDX)) { + struct { + struct vring_avail *avail; + unsigned int num; + } vr; + + vr.avail = avail; + vr.num = q->num; + + needs_interrupt = + vring_need_event(vring_used_event((&vr)), + used->idx, old_used_idx); + log_debug("EVENT_IDX is enabled; driver wants event " + "at %u needs_interrupt %d\n", + vring_used_event(&vr), needs_interrupt); + } + + if (needs_interrupt) { + log_debug("sending VRING interrupt\n"); + priv->interrupt_status |= VIRTIO_MMIO_INT_VRING; + } + } +} + +long h_read(void *ctx, const void *addr, enum sandboxio_size_t size) +{ + struct udevice *dev = ctx; + struct udevice *emul_dev = dev_get_parent(dev); + struct sandbox_emul_priv *priv = dev_get_priv(dev); + ulong offset = (ulong)addr - (ulong)priv->mmio.base; + struct virtio_emul_ops *ops = virtio_emul_get_ops(emul_dev); + struct virtio_emul_queue *q; + u32 val = 0; + + if (offset >= VIRTIO_MMIO_CONFIG) { + ulong config_offset = offset - VIRTIO_MMIO_CONFIG; + int ret; + + ret = ops->get_config(emul_dev, config_offset, &val, size); + if (ret) + log_warning("Failed to process request (err=%dE)\n", + ret); + return val; + } + + if (priv->queue_sel >= priv->num_queues) { + log_debug("invalid queue_sel %d\n", priv->queue_sel); + return 0; + } + q = &priv->queues[priv->queue_sel]; + + switch (offset) { + case VIRTIO_MMIO_MAGIC_VALUE: + return ('v' | 'i' << 8 | 'r' << 16 | 't' << 24); + case VIRTIO_MMIO_VERSION: + return 2; + case VIRTIO_MMIO_DEVICE_ID: + return ops->get_device_id(emul_dev); + case VIRTIO_MMIO_VENDOR_ID: + return VENDOR_ID; + case VIRTIO_MMIO_DEVICE_FEATURES: + return !priv->features_sel ? + (priv->features & 0xffffffff) : + (priv->features >> 32); + case VIRTIO_MMIO_QUEUE_NUM_MAX: + return QUEUE_MAX_SIZE; + case VIRTIO_MMIO_QUEUE_READY: + return q->ready; + case VIRTIO_MMIO_INTERRUPT_STATUS: + return priv->interrupt_status; + case VIRTIO_MMIO_STATUS: + return priv->status; + case VIRTIO_MMIO_QUEUE_DESC_LOW: + return q->desc_addr & 0xffffffff; + case VIRTIO_MMIO_QUEUE_DESC_HIGH: + return q->desc_addr >> 32; + case VIRTIO_MMIO_QUEUE_AVAIL_LOW: + return q->avail_addr & 0xffffffff; + case VIRTIO_MMIO_QUEUE_AVAIL_HIGH: + return q->avail_addr >> 32; + case VIRTIO_MMIO_QUEUE_USED_LOW: + return q->used_addr & 0xffffffff; + case VIRTIO_MMIO_QUEUE_USED_HIGH: + return q->used_addr >> 32; + case VIRTIO_MMIO_CONFIG_GENERATION: + return priv->config_generation; + default: + log_debug("unhandled read from offset 0x%lx\n", offset); + return 0; + } +} + +void h_write(void *ctx, void *addr, unsigned int val, + enum sandboxio_size_t size) +{ + struct udevice *dev = ctx; + struct udevice *emul_dev = dev_get_parent(dev); + struct sandbox_emul_priv *priv = dev_get_priv(dev); + ulong offset = (ulong)addr - (ulong)priv->mmio.base; + struct virtio_emul_queue *q; + + if (offset >= VIRTIO_MMIO_CONFIG) + return; + + if (priv->queue_sel >= priv->num_queues && offset != VIRTIO_MMIO_QUEUE_SEL) + return; + q = &priv->queues[priv->queue_sel]; + + switch (offset) { + case VIRTIO_MMIO_DEVICE_FEATURES_SEL: + priv->features_sel = val; + break; + case VIRTIO_MMIO_DRIVER_FEATURES: + if (priv->features_sel == 0) + priv->driver_features = (priv->driver_features & + 0xffffffff00000000) | val; + else + priv->driver_features = (priv->driver_features & + 0xffffffff) | ((u64)val << 32); + break; + case VIRTIO_MMIO_DRIVER_FEATURES_SEL: + priv->features_sel = val; + break; + case VIRTIO_MMIO_QUEUE_SEL: + if (val < priv->num_queues) + priv->queue_sel = val; + else + log_debug("tried to select invalid queue %u\n", val); + break; + case VIRTIO_MMIO_QUEUE_NUM: + q->num = (val > 0 && val <= QUEUE_MAX_SIZE) ? val : 0; + break; + case VIRTIO_MMIO_QUEUE_READY: + q->ready = val & 0x1; + break; + case VIRTIO_MMIO_QUEUE_NOTIFY: + process_queue(emul_dev, priv, val); + break; + case VIRTIO_MMIO_INTERRUPT_ACK: + priv->interrupt_status &= ~val; + break; + case VIRTIO_MMIO_STATUS: + priv->status = val; + break; + case VIRTIO_MMIO_QUEUE_DESC_LOW: + q->desc_addr = (q->desc_addr & 0xffffffff00000000) | val; + break; + case VIRTIO_MMIO_QUEUE_DESC_HIGH: + q->desc_addr = (q->desc_addr & 0xffffffff) | ((u64)val << 32); + break; + case VIRTIO_MMIO_QUEUE_AVAIL_LOW: + q->avail_addr = (q->avail_addr & 0xffffffff00000000) | val; + break; + case VIRTIO_MMIO_QUEUE_AVAIL_HIGH: + q->avail_addr = (q->avail_addr & 0xffffffff) | ((u64)val << 32); + break; + case VIRTIO_MMIO_QUEUE_USED_LOW: + q->used_addr = (q->used_addr & 0xffffffff00000000) | val; + break; + case VIRTIO_MMIO_QUEUE_USED_HIGH: + q->used_addr = (q->used_addr & 0xffffffff) | ((u64)val << 32); + break; + default: + log_debug("unhandled write to offset 0x%lx\n", offset); + break; + } +} + +static int sandbox_emul_of_to_plat(struct udevice *dev) +{ + struct udevice *emul_dev = dev_get_parent(dev); + struct virtio_emul_ops *ops = virtio_emul_get_ops(emul_dev); + struct sandbox_emul_priv *priv = dev_get_priv(dev); + int ret; + + /* set up the MMIO base so that virtio_mmio_probe() can find it */ + priv->mmio.base = memalign(SZ_4K, MMIO_SIZE); + if (!priv->mmio.base) + return -ENOMEM; + + ret = sandbox_mmio_add(priv->mmio.base, MMIO_SIZE, h_read, h_write, + dev); + if (ret) { + free(priv->mmio.base); + return log_msg_ret("sep", ret); + } + + priv->num_queues = MAX_VIRTIO_QUEUES; + priv->features = BIT(VIRTIO_F_VERSION_1) | + BIT(VIRTIO_RING_F_EVENT_IDX) | + ops->get_features(emul_dev); + + log_debug("sandbox virtio emulator, mmio %p\n", priv->mmio.base); + + return 0; +} + +static int sandbox_emul_remove(struct udevice *dev) +{ + sandbox_mmio_remove(dev); + + return 0; +} + +static const struct udevice_id virtio_sandbox2_ids[] = { + { .compatible = "sandbox,virtio-emul" }, + { } +}; + +U_BOOT_DRIVER(virtio_emul) = { + .name = "virtio-emul", + .id = UCLASS_VIRTIO, + .of_match = virtio_sandbox2_ids, + .probe = virtio_mmio_probe, + .remove = sandbox_emul_remove, + .ops = &virtio_mmio_ops, + .of_to_plat = sandbox_emul_of_to_plat, + .priv_auto = sizeof(struct sandbox_emul_priv), +}; + +UCLASS_DRIVER(virtio_emul) = { + .name = "virtio_emul", + .id = UCLASS_VIRTIO_EMUL, +#if CONFIG_IS_ENABLED(OF_REAL) + .post_bind = dm_scan_fdt_dev, +#endif +}; diff --git a/drivers/virtio/sandbox_emul.h b/drivers/virtio/sandbox_emul.h new file mode 100644 index 00000000000..d0a841a38df --- /dev/null +++ b/drivers/virtio/sandbox_emul.h @@ -0,0 +1,110 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * VirtIO Sandbox emulator, for testing purpose only. This emulates the QEMU + * side of virtio, using the MMIO driver and handling any accesses + * + * This handles traffic from the virtio_ring + * + * Copyright 2025 Simon Glass <sjg@chromium.org> + */ + +#ifndef __SANDBOX_EMUL_H +#define __SANDBOX_EMUL_H + +#include "virtio_mmio.h" +#include "virtio_types.h" + +enum sandboxio_size_t; +struct udevice; +struct vring_desc; + +enum { + MAX_VIRTIO_QUEUES = 8, + QUEUE_MAX_SIZE = 256, +}; + +/** + * struct virtio_emul_queue - Emulator's state for a single virtqueue + */ +struct virtio_emul_queue { + __virtio32 num; + __virtio32 ready; + __virtio64 desc_addr; + __virtio64 avail_addr; + __virtio64 used_addr; + __virtio16 last_avail_idx; // Device's internal counter +}; + +/** + * struct sandbox_emul_priv - Private info for the emulator + */ +struct sandbox_emul_priv { + struct virtio_mmio_priv mmio; + int num_queues; + int queue_sel; + u32 status; + u64 features_sel; + u64 features; + u64 driver_features; + u32 interrupt_status; + u32 config_generation; + struct virtio_emul_queue queues[MAX_VIRTIO_QUEUES]; +}; + +/** + * struct virtio_emul_ops - Operations for a virtio device emulator + * + * @process_request: + * @get_config: Reads from the device-specific configuration space + * @get_features: Returns the device-specific feature bits + */ +struct virtio_emul_ops { + /** + * process_request() - Handles a single request from the driver + * + * @dev: The emulator device + * @descs: Pointer to the virtqueue's descriptor table + * @head_idx: The index of the first descriptor in the chain for + * this request + * @writtenp: Returns the total number of bytes written by the + * device into the driver's buffers (e.g. for a read + * request and the status byte). This is what will be + * placed in the `len` field of the used ring element. + * @return 0 on success, negative on error. + */ + int (*process_request)(struct udevice *dev, struct vring_desc *descs, + u32 head_idx, int *writtenp); + + /** + * get_config() - Reads from the device-specific configuration space + * + * @dev: The emulator device + * @offset: The byte offset into the configuration space to read from + * @buf: The buffer to copy the configuration data into + * @size: The number of bytes to read + * @return 0 on success, negative on error. + */ + int (*get_config)(struct udevice *dev, ulong offset, void *buf, + enum sandboxio_size_t size); + + /** + * get_features() - Returns the device-specific feature bits + * + * @dev: The emulator device + * @return A bitmask of the device-specific features to be OR'd + * with the transport features. + */ + u64 (*get_features)(struct udevice *dev); + + /** + * get_device_id() - Returns the virtio device ID + * + * @dev: The emulator device + * @return The virtio device ID for this emulator + */ + u32 (*get_device_id)(struct udevice *dev); +}; + +#define virtio_emul_get_ops(dev) ((struct virtio_emul_ops *)(dev)->driver->ops) + +#endif diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h index 49f98cd2e1a..4818d9bd272 100644 --- a/include/dm/uclass-id.h +++ b/include/dm/uclass-id.h @@ -35,6 +35,7 @@ enum uclass_id { UCLASS_USB_EMUL, /* sandbox USB bus device emulator */ UCLASS_AXI_EMUL, /* sandbox AXI bus device emulator */ UCLASS_FFA_EMUL, /* sandbox FF-A device emulator */ + UCLASS_VIRTIO_EMUL, /* Emulator for a virtIO transport device */ /* U-Boot uclasses start here - in alphabetical order */ UCLASS_ACPI_PMC, /* (x86) Power-management controller (PMC) */ -- 2.43.0

From: Simon Glass <sjg@chromium.org> Add an emulator driver for block devices, so that sandbox can test these fully. The emulator uses MMIO to communicate with the controlling virtio device. Signed-off-by: Simon Glass <sjg@chromium.org> --- configs/sandbox_defconfig | 1 + drivers/virtio/Makefile | 2 +- drivers/virtio/emul_blk.c | 153 ++++++++++++++++++++++++++++++++++++ drivers/virtio/virtio_blk.h | 3 + 4 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 drivers/virtio/emul_blk.c diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index 069536b2cdd..060fa0f9934 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -363,3 +363,4 @@ CONFIG_TEST_FDTDEC=y CONFIG_UNIT_TEST=y CONFIG_UT_TIME=y CONFIG_UT_DM=y +CONFIG_VIRTIO_BLK=y diff --git a/drivers/virtio/Makefile b/drivers/virtio/Makefile index d928c7b0ad2..4709e16f789 100644 --- a/drivers/virtio/Makefile +++ b/drivers/virtio/Makefile @@ -8,7 +8,7 @@ obj-$(CONFIG_VIRTIO_MMIO) += virtio_mmio.o obj-$(CONFIG_VIRTIO_PCI) += virtio_pci_modern.o obj-$(CONFIG_VIRTIO_PCI_LEGACY) += virtio_pci_legacy.o obj-$(CONFIG_VIRTIO_SANDBOX) += virtio_sandbox.o -obj-$(CONFIG_VIRTIO_SANDBOX_EMUL) += sandbox_emul.o +obj-$(CONFIG_VIRTIO_SANDBOX_EMUL) += sandbox_emul.o emul_blk.o obj-$(CONFIG_VIRTIO_NET) += virtio_net.o obj-$(CONFIG_VIRTIO_BLK) += virtio_blk.o obj-$(CONFIG_VIRTIO_RNG) += virtio_rng.o diff --git a/drivers/virtio/emul_blk.c b/drivers/virtio/emul_blk.c new file mode 100644 index 00000000000..1d42b34b8ab --- /dev/null +++ b/drivers/virtio/emul_blk.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Emulation of a block device. This implements a simple version of the QEMU + * side of the interface. + * + * Copyright 2025 Simon Glass <sjg@chromium.org> + */ + +#define LOG_CATEGORY UCLASS_VIRTIO + +#include <dm.h> +#include <malloc.h> +#include <asm/io.h> +#include <dt-bindings/virtio.h> +#include <linux/sizes.h> +#include "virtio_blk.h" +#include "virtio_ring.h" +#include "sandbox_emul.h" + +enum { + DISK_SIZE_MB = 1, + SECTOR_SIZE = 512, +}; + +/** + * struct virtio_blk_emul_priv - private data for the block emulator + * + * @config: virtio block-device-configuration structure, exposed to the driver + * through the config space + * @disk_data: allocated memory for the virtual disk + * @disk_size: total size of the virtual disk in bytes + */ +struct virtio_blk_emul_priv { + struct virtio_blk_config config; + void *disk_data; + u64 disk_size; +}; + +static int blk_emul_process_request(struct udevice *dev, + struct vring_desc *descs, u32 head_idx, + int *writtenp) +{ + struct virtio_blk_emul_priv *priv = dev_get_priv(dev); + struct vring_desc *hdr_desc, *data_desc, *status_desc; + struct virtio_blk_outhdr *hdr; + void *data_buf; + u64 offset; + u8 *status; + + hdr_desc = &descs[head_idx]; + if (!(hdr_desc->flags & VRING_DESC_F_NEXT)) + return -EIO; + data_desc = &descs[hdr_desc->next]; + if (!(data_desc->flags & VRING_DESC_F_NEXT)) + return -EIO; + status_desc = &descs[data_desc->next]; + + hdr = (struct virtio_blk_outhdr *)hdr_desc->addr; + status = (u8 *)status_desc->addr; + + offset = hdr->sector * SECTOR_SIZE; + if (offset + data_desc->len > priv->disk_size) { + *status = VIRTIO_BLK_S_IOERR; + *writtenp = 1; + return 0; + } + + data_buf = (void *)data_desc->addr; + + switch (hdr->type) { + case VIRTIO_BLK_T_IN: + log_debug("read: sector %lld, len %u\n", hdr->sector, + data_desc->len); + memcpy(data_buf, priv->disk_data + offset, data_desc->len); + *writtenp = data_desc->len; + break; + case VIRTIO_BLK_T_OUT: + log_debug("write: sector %lld, len %u\n", hdr->sector, + data_desc->len); + memcpy(priv->disk_data + offset, data_buf, data_desc->len); + *writtenp = 0; + break; + default: + log_warning("unknown request type 0x%x\n", hdr->type); + *status = VIRTIO_BLK_S_UNSUPP; + *writtenp = 1; + return 0; + } + + *status = VIRTIO_BLK_S_OK; + *writtenp += 1; /* For the status byte */ + + return 0; +} + +static int blk_emul_get_config(struct udevice *dev, ulong offset, void *buf, + enum sandboxio_size_t size) +{ + struct virtio_blk_emul_priv *priv = dev_get_priv(dev); + + if (offset + size > sizeof(priv->config)) + return -EIO; + + memcpy(buf, (u8 *)&priv->config + offset, size); + + return 0; +} + +static u64 blk_emul_get_features(struct udevice *dev) +{ + return BIT(VIRTIO_BLK_F_BLK_SIZE); +} + +static u32 blk_emul_get_device_id(struct udevice *dev) +{ + return VIRTIO_ID_BLOCK; +} + +static int virtio_blk_emul_probe(struct udevice *dev) +{ + struct virtio_blk_emul_priv *priv = dev_get_priv(dev); + + priv->disk_size = (u64)DISK_SIZE_MB * SZ_1M; + priv->disk_data = calloc(1, priv->disk_size); + if (!priv->disk_data) + return -ENOMEM; + + priv->config.capacity = priv->disk_size / SECTOR_SIZE; + priv->config.blk_size = SECTOR_SIZE; + + return 0; +} + +static struct virtio_emul_ops blk_emul_ops = { + .process_request = blk_emul_process_request, + .get_config = blk_emul_get_config, + .get_features = blk_emul_get_features, + .get_device_id = blk_emul_get_device_id, +}; + +static const struct udevice_id virtio_blk_emul_ids[] = { + { .compatible = "sandbox,virtio-blk-emul" }, + { } +}; + +U_BOOT_DRIVER(virtio_blk_emul) = { + .name = "virtio_blk_emul", + .id = UCLASS_VIRTIO_EMUL, + .of_match = virtio_blk_emul_ids, + .probe = virtio_blk_emul_probe, + .ops = &blk_emul_ops, + .priv_auto = sizeof(struct virtio_blk_emul_priv), +}; diff --git a/drivers/virtio/virtio_blk.h b/drivers/virtio/virtio_blk.h index b37ba264df4..cbb6996ebde 100644 --- a/drivers/virtio/virtio_blk.h +++ b/drivers/virtio/virtio_blk.h @@ -9,6 +9,9 @@ #ifndef _LINUX_VIRTIO_BLK_H #define _LINUX_VIRTIO_BLK_H +#include <compiler.h> +#include "virtio_types.h" + /* Feature bits */ #define VIRTIO_BLK_F_SIZE_MAX 1 /* Indicates maximum segment size */ #define VIRTIO_BLK_F_SEG_MAX 2 /* Indicates maximum # of segments */ -- 2.43.0

From: Simon Glass <sjg@chromium.org> Add the devicetree snippet to include the new virtio block-device emulator in the test devicetree. Signed-off-by: Simon Glass <sjg@chromium.org> --- arch/sandbox/dts/test.dts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts index d0384e9b7e1..c880cc56818 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -1704,6 +1704,14 @@ virtio-type = <VIRTIO_ID_BLOCK>; }; + virtio-blk { + compatible = "sandbox,virtio-blk-emul"; + + mmio { + compatible = "sandbox,virtio-emul"; + }; + }; + sandbox_scmi { compatible = "sandbox,scmi-devices"; power-domains = <&pwrdom_scmi 2>; -- 2.43.0
participants (1)
-
sjg@u-boot.org