diff --git a/.gitignore b/.gitignore index 087d952d0..9039abf35 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,8 @@ src/platform/__board.rs # systemtest rootfs1.zip* jenkins/__pycache__ +jenkins/logs/ +logs/ jenkins-cli.jar tools/kconfig/.venv/ tools/kconfig/__pycache__/ diff --git a/Jenkinsfile b/Jenkinsfile index a1304dbc8..c633f7ae7 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -32,6 +32,33 @@ def matrixCellDir() { return "${env.WORKSPACE}/.matrix/${bid.replace('/', '__')}" } +/** Stable directory name for a BID under ``$WORKSPACE/logs/``. */ +def bidLogKey(String bid = env.BID) { + return (bid ?: '').replace('/', '__') +} + +/** Copy per-cell logs out to ``$WORKSPACE/logs/`` before ``.matrix`` is removed. */ +def collectCellLogs() { + def src = "${matrixCellDir()}/logs" + def dest = "${env.WORKSPACE}/logs/${bidLogKey()}" + def logsRoot = "${env.WORKSPACE}/logs" + sh """ + if [ ! -d '${src}' ]; then + echo "No cell logs to collect: ${src}" + exit 0 + fi + mkdir -p '${dest}' 2>/dev/null || sudo mkdir -p '${dest}' + sudo chown -R "\$(id -u):\$(id -g)" '${logsRoot}' 2>/dev/null || true + # Source may be root-owned after board sudo ci_runner. + if ! cp -a '${src}/.' '${dest}/' 2>/dev/null; then + sudo cp -a '${src}/.' '${dest}/' + sudo chown -R "\$(id -u):\$(id -g)" '${dest}' + fi + echo "Collected cell logs -> ${dest}" + ls -la '${dest}' || true + """ +} + /** Isolated workspace for top-level CI jobs (linter, license-checker, …). */ def jenkinsJobDir(String name) { return "${env.WORKSPACE}/.jenkins/${name}" @@ -45,6 +72,9 @@ def syncWorkspaceTo(String destDir) { --exclude '.jenkins/' \\ --exclude '.matrix/' \\ --exclude '.jenkins-matrix/' \\ + --exclude 'logs/' \\ + --exclude '__pycache__/' \\ + --exclude '*.pyc' \\ '${env.WORKSPACE}/' '${destDir}/' """ } @@ -188,7 +218,9 @@ pipeline { always { echo "=== DEBUG: Branch ${env.BRANCH_NAME} ===" echo "=== DEBUG: Commit ${env.GIT_COMMIT} ===" - deleteDir() + // Keep $WORKSPACE/logs (collected from cells); only tear down sandboxes. + sh 'sudo rm -rf .matrix 2>/dev/null || rm -rf .matrix || true' + archiveArtifacts artifacts: 'logs/**/*', allowEmptyArchive: true } } @@ -205,6 +237,8 @@ pipeline { LOONGARCH64_TOOLCHAIN_PATH = '/home/light/DEMO/toolchain/loongarch_cross_tools' // All toolchain bins on PATH; same for every matrix cell (no per-arch selection). TOOLCHAIN_PATHS = "${env.RISCV_TOOLCHAIN_PATH}/bin:${env.AARCH64_TOOLCHAIN_PATH}/bin:${env.LOONGARCH64_TOOLCHAIN_PATH}/bin" + TFTP_DIR = '/home/light/tftp' + PYTHONDONTWRITEBYTECODE = '1' } stages { @@ -385,8 +419,14 @@ pipeline { "${prepareScript}" """ } else if (mode == 'board') { - // Placeholder for future board artifact distribution by network. - echo "Board prepare placeholder [BID=${env.BID}]" + echo "Deploy TFTP artifacts [BID=${env.BID}, TFTP_DIR=${env.TFTP_DIR}]" + sh """ + export TERM=\${TERM:-xterm} + sudo mkdir -p "${env.TFTP_DIR}" + sudo make cp ARCH=${arch} BOARD=${board} MODE=release TFTP_DIR="${env.TFTP_DIR}" + sudo cp platform/${arch}/${board}/image/dts/rk3568_limit_zone0.dtb "${env.TFTP_DIR}/" + sudo cp ${kdir}/arch/arm64/boot/Image "${env.TFTP_DIR}/" + """ } else { error("jenkins/ci.yaml BID=${env.BID}: unsupported tests.mode='${mode}'") } @@ -402,14 +442,24 @@ pipeline { steps { dir(matrixCellDir()) { script { - echo "Run tests via ci_runner [BID=${env.BID}]" - sh """ - export TERM=\${TERM:-xterm} - ${toolchainPathShell()} - ${qemuPathShell()} - python3 jenkins/ci_runner.py \ - --bid "${env.BID}" - """ + def bidCfg = getBidConfig(loadCiYaml(), env.BID) + def mode = (bidCfg.tests?.mode ?: '').toString().trim() + echo "Run tests via ci_runner [BID=${env.BID}, mode=${mode}]" + if (mode == 'board') { + sh """ + export TERM=\${TERM:-xterm} + sudo -E python3 jenkins/ci_runner.py \ + --bid "${env.BID}" + """ + } else { + sh """ + export TERM=\${TERM:-xterm} + ${toolchainPathShell()} + ${qemuPathShell()} + python3 jenkins/ci_runner.py \ + --bid "${env.BID}" + """ + } } } } @@ -418,7 +468,10 @@ pipeline { post { always { - script { finishGithubCheck(matrixCheckName(), currentBuild.currentResult) } + script { + collectCellLogs() + finishGithubCheck(matrixCheckName(), currentBuild.currentResult) + } } } } diff --git a/Makefile b/Makefile index 4fb832a86..1dc8758c4 100644 --- a/Makefile +++ b/Makefile @@ -66,13 +66,28 @@ ifeq ($(MODE), release) build_args += --release endif -# color code -COLOR_GREEN := $(shell tput setaf 2) -COLOR_RED := $(shell tput setaf 1) -COLOR_YELLOW := $(shell tput setaf 3) -COLOR_BLUE := $(shell tput setaf 4) -COLOR_BOLD := $(shell tput bold) -COLOR_RESET := $(shell tput sgr0) +# color code (skip tput when TERM is unset, e.g. Jenkins sh steps) +ifdef TERM +COLOR_GREEN := $(shell tput setaf 2 2>/dev/null) +COLOR_RED := $(shell tput setaf 1 2>/dev/null) +COLOR_YELLOW := $(shell tput setaf 3 2>/dev/null) +COLOR_BLUE := $(shell tput setaf 4 2>/dev/null) +COLOR_BOLD := $(shell tput bold 2>/dev/null) +COLOR_RESET := $(shell tput sgr0 2>/dev/null) +else +COLOR_GREEN := +COLOR_RED := +COLOR_YELLOW := +COLOR_BLUE := +COLOR_BOLD := +COLOR_RESET := +endif + +# Defconfig / menuconfig: see tools/kconfig/kconfig_cli.py +kconfig_python := tools/kconfig/.venv/bin/python + +# Defconfig / menuconfig: see tools/kconfig/kconfig_cli.py +kconfig_python := tools/kconfig/.venv/bin/python # Defconfig / menuconfig: see tools/kconfig/kconfig_cli.py kconfig_python := tools/kconfig/.venv/bin/python @@ -187,8 +202,11 @@ monitor: jlink-server: JLinkGDBServer -select USB -if JTAG -device Cortex-A53 -port 1234 +TFTP_DIR ?= $(HOME)/tftp + cp: - cp $(hvisor_bin) ~/tftp + @mkdir -p "$(TFTP_DIR)" + cp $(hvisor_bin) "$(TFTP_DIR)/" test-pre: download-test-img chmod +x platform/$(ARCH)/$(BOARD)/test/runner.sh diff --git a/jenkins/board_power.sh b/jenkins/board_power.sh new file mode 100755 index 000000000..0eb7a28bc --- /dev/null +++ b/jenkins/board_power.sh @@ -0,0 +1,32 @@ +#!/bin/sh +# Relay power control for board CI (socat hex frames). +# Usage: board_power.sh off|on|cycle +# cycle: off, wait 3s, on + +set -eu + +action=${1:?action required (off|on|cycle)} +port=${2:?serial port required, e.g. /dev/ttyUSB1} + +send_frame() { + frame=$1 + printf '%b' "$frame" | sudo socat - "$port" +} + +case "$action" in + off) + send_frame '\xA0\x04\x00\xA4' + ;; + on) + send_frame '\xA0\x04\x01\xA5' + ;; + cycle) + send_frame '\xA0\x04\x00\xA4' + sleep 3 + send_frame '\xA0\x04\x01\xA5' + ;; + *) + echo "usage: $0 off|on|cycle " >&2 + exit 1 + ;; +esac diff --git a/jenkins/board_scp.sh b/jenkins/board_scp.sh new file mode 100755 index 000000000..12a568740 --- /dev/null +++ b/jenkins/board_scp.sh @@ -0,0 +1,97 @@ +#!/bin/sh +# Stage zone1 test artifacts on the CI host for board pull via scp. +# Zone0 boot Image, Image and rootfs2.ext4 are large and +# persistent on the board; do not re-stage them every CI run. + +set -eux + +ARCH=${ARCH:?ARCH is required} +BOARD=${BOARD:?BOARD is required} +WORKSPACE_ROOT=${WORKSPACE_ROOT:-$(pwd)} +HVISOR_TOOL_PATH=${HVISOR_TOOL_PATH:-${WORKSPACE_ROOT}/hvisor-tool} +MODE=${MODE:-release} +STAGING_DIR=${STAGING_DIR:-/home/light/tftp/ci_deploy} + +case "${HVISOR_TOOL_PATH}" in + /*) ;; + *) HVISOR_TOOL_PATH="${WORKSPACE_ROOT}/${HVISOR_TOOL_PATH}" ;; +esac + +PLATFORM_DIR="${WORKSPACE_ROOT}/platform/${ARCH}/${BOARD}" +CONFIGS_DIR="${PLATFORM_DIR}/configs" +IMAGE_DIR="${PLATFORM_DIR}/image" +SCRIPTS_DIR="${PLATFORM_DIR}/scripts" +ZONE1_BOOT_SCRIPT="${SCRIPTS_DIR}/boot_zone1.sh" +CHECK_SERIAL_SCRIPT="${WORKSPACE_ROOT}/jenkins/check_serial.sh" + +case "${ARCH}" in + x86_64) RUSTC_TARGET="x86_64-unknown-none" ;; + aarch64) RUSTC_TARGET="aarch64-unknown-none" ;; + riscv64) RUSTC_TARGET="riscv64gc-unknown-none-elf" ;; + *) + echo "error: unsupported ARCH: ${ARCH}" + exit 1 + ;; +esac + +BUILD_PATH="${WORKSPACE_ROOT}/target/${RUSTC_TARGET}/${MODE}" +HVISOR_BIN="${BUILD_PATH}/hvisor.bin" + +if [ -n "${ZONE1_DTB:-}" ]; then + : +elif [ -f "${IMAGE_DIR}/dts/rk3568_limit_zone1.dtb" ]; then + ZONE1_DTB="${IMAGE_DIR}/dts/rk3568_limit_zone1.dtb" +elif [ -f "${IMAGE_DIR}/dts/zone1-linux.dtb" ]; then + ZONE1_DTB="${IMAGE_DIR}/dts/zone1-linux.dtb" +else + ZONE1_DTB="${IMAGE_DIR}/dts/zone1-linux.dtb" +fi + +echo "ARCH: ${ARCH}" +echo "BOARD: ${BOARD}" +echo "HVISOR_TOOL_PATH: ${HVISOR_TOOL_PATH}" +echo "STAGING_DIR: ${STAGING_DIR}" + +if [ ! -f "${HVISOR_TOOL_PATH}/output/hvisor" ]; then + echo "error: hvisor tool binary not found: ${HVISOR_TOOL_PATH}/output/hvisor" + exit 1 +fi +if [ ! -f "${HVISOR_TOOL_PATH}/output/hvisor.ko" ]; then + echo "error: hvisor.ko not found: ${HVISOR_TOOL_PATH}/output/hvisor.ko" + exit 1 +fi +if [ ! -f "${HVISOR_BIN}" ]; then + echo "error: hvisor.bin not found: ${HVISOR_BIN}" + exit 1 +fi +if [ ! -f "${ZONE1_BOOT_SCRIPT}" ]; then + echo "error: boot script not found: ${ZONE1_BOOT_SCRIPT}" + exit 1 +fi + +if [ ! -f "${ZONE1_DTB}" ] && [ -d "${IMAGE_DIR}/dts" ]; then + echo "zone1 dtb is missing, building from ${IMAGE_DIR}/dts" + make -C "${IMAGE_DIR}/dts" all || true +fi + +rm -rf "${STAGING_DIR}" +mkdir -p "${STAGING_DIR}" + +cp "${HVISOR_TOOL_PATH}/output/hvisor" "${HVISOR_TOOL_PATH}/output/hvisor.ko" "${STAGING_DIR}/" +cp "${HVISOR_BIN}" "${STAGING_DIR}/" +cp "${CONFIGS_DIR}/"* "${STAGING_DIR}/" +cp "${ZONE1_BOOT_SCRIPT}" "${STAGING_DIR}/" + +if [ -f "${ZONE1_DTB}" ]; then + cp "${ZONE1_DTB}" "${STAGING_DIR}/" +else + echo "warning: zone1 dtb unavailable, skip copying ${ZONE1_DTB}" +fi + +if [ -f "${CHECK_SERIAL_SCRIPT}" ]; then + cp "${CHECK_SERIAL_SCRIPT}" "${STAGING_DIR}/" +fi + +chmod -R a+rX "${STAGING_DIR}" +echo "board staging completed: ${STAGING_DIR}" +ls -la "${STAGING_DIR}" diff --git a/jenkins/check_serial.sh b/jenkins/check_serial.sh new file mode 100755 index 000000000..3cb9911fa --- /dev/null +++ b/jenkins/check_serial.sh @@ -0,0 +1,49 @@ +#!/bin/sh +# Non-interactive virtio-console check for inner zone (/dev/pts/x). +# Usage: check_serial.sh [timeout_sec] [command...] + +set -eu + +pts_dev=${1:?pts device required} +log_file=${2:?log file required} +shift 2 + +prompt_timeout=60 +if [ $# -gt 0 ] && [ "$1" -eq "$1" ] 2>/dev/null; then + prompt_timeout=$1 + shift +fi + +: > "$log_file" + +read_pts() { + timeout "${1:-2}" cat "$pts_dev" 2>/dev/null >> "$log_file" || true +} + +has_console_ready() { + tr -d '\r' < "$log_file" | sed 's/\x1b\[[0-9;?]*[ -\/]*[@-~]//g' \ + | grep -qE 'root@[^[:space:]]*[#$][[:space:]]*$|^[[:space:]]*#[[:space:]]*$|login:[[:space:]]*$' +} + +deadline=$(( $(date +%s) + prompt_timeout )) +while [ "$(date +%s)" -lt "$deadline" ]; do + read_pts 2 + if has_console_ready; then + break + fi + sleep 0.2 +done + +if ! has_console_ready; then + echo "check_serial: timed out after ${prompt_timeout}s waiting for console prompt on $pts_dev" \ + >> "$log_file" + exit 1 +fi + +if [ $# -gt 0 ]; then + printf '%s\r\n' "$@" + sleep 1 + read_pts 10 +fi + +exit 0 diff --git a/jenkins/ci.yaml b/jenkins/ci.yaml index b23c396e0..356d4d835 100644 --- a/jenkins/ci.yaml +++ b/jenkins/ci.yaml @@ -14,21 +14,38 @@ bids: tests: mode: qemu uboot_cmd: bootm 0x40400000 - 0x40000000 + uboot_ready_pattern: => cases: - zone0_start - zone1_start - # - bid: aarch64/rk3568 - # build_args: - # - KDIR=/home/light/DEMO/sdk/rk356x-up4-2c/kernel - # tests: - # mode: board - # serial: /dev/serial/by-id/usb-1a86_USB2.0-Ser_-if00-port0 - # baudrate: 115200 - # uboot_cmd: pci enum;setenv serverip 192.168.0.1; setenv ipaddr 192.168.0.2; setenv loadaddr 0x60800000; setenv fdt_addr 0xa0000000; setenv zone0_kernel_addr 0x00280000; tftp ${loadaddr} ${serverip}:hvisor.bin; tftp ${fdt_addr} ${serverip}:rk3568_limit_zone0.dtb; tftp ${zone0_kernel_addr} ${serverip}:Image_test5; bootm ${loadaddr} - ${fdt_addr}; - # cases: - # - zone0_start - # - zone1_start + - bid: aarch64/rk3568 + build_args: + - KDIR=/home/light/DEMO/sdk/rk356x-up4-2c/kernel + tests: + mode: board + serial: /dev/serial/by-id/usb-1a86_USB2.0-Ser_-if00-port0 + power_serial: /dev/serial/by-id/usb-1a86_USB_Serial-if00-port0 + baudrate: 1500000 + uboot_ready_pattern: '=>' + uboot_cmd: pci enum;setenv serverip 192.168.1.181; setenv ipaddr 192.168.1.240; setenv loadaddr 0x60800000; setenv fdt_addr 0xa0000000; setenv zone0_kernel_addr 0x00280000; tftp ${loadaddr} ${serverip}:hvisor.bin; tftp ${fdt_addr} ${serverip}:rk3568_limit_zone0.dtb; tftp ${zone0_kernel_addr} ${serverip}:Image; bootm ${loadaddr} - ${fdt_addr}; + network: + host_ip: 192.168.1.181 + host_user: light + staging_dir: /home/light/tftp/ci_deploy + zone1_dtb: platform/aarch64/rk3568/image/dts/rk3568_limit_zone1.dtb + lspci: + expected_bdfs: + - "0001:10:00.0" + - "0001:11:00.0" + - "0002:20:00.0" + - "0002:21:00.0" + min_count: 4 + cases: + - zone0_start + - network + - lspci + - zone1_start - bid: x86_64/qemu @@ -38,6 +55,6 @@ bids: mode: qemu cases: - zone0_start - - zone1_start + - zone1_start diff --git a/jenkins/ci_runner.py b/jenkins/ci_runner.py index 6036ff9c4..e628f4459 100755 --- a/jenkins/ci_runner.py +++ b/jenkins/ci_runner.py @@ -17,6 +17,18 @@ CaseFunc = Callable[[dict[str, Any], Terminal | None], int] +# Wait for zone1 inner console: shell prompt (#/$) or login:. +ZONE0_READY_PATTERN = r"root@[^\r\n]*[#$]\s|(?:\r?\n)#\s" +ZONE1_INNER_PROMPT_TIMEOUT = 60.0 +ZONE1_INNER_LOG_FETCH_TIMEOUT = 30.0 + + +def logs_dir(cfg: dict[str, Any]) -> Path: + """Per-cell log directory: ``/logs``.""" + path = Path(cfg["workspace"]) / "logs" + path.mkdir(parents=True, exist_ok=True) + return path + def wait_qemu_socket(path: str, timeout: float = 30.0) -> None: deadline = time.monotonic() + timeout @@ -51,110 +63,277 @@ def terminate_managed_process(cfg: dict[str, Any]) -> None: pass -def run_and_print(term: Terminal, command: str) -> str: - output = term.send_until_quiet(command, quiet_seconds=1.0, max_duration=40.0) - if output: - print(output, end="", flush=True) - return output - - -def run_and_print_quiet( - term: Terminal, - command: str, - quiet_seconds: float = 1.0, - max_duration: float = 30.0, - check_exit: bool = True, -) -> tuple[str, int]: - # Send a leading Enter to synchronize shell prompt state. - # term.send("\n") - # _ = term.read_for(duration=0.2) - - output, rc = term.run_until_quiet_with_status( - command, - quiet_seconds=quiet_seconds, - max_duration=max_duration, - ) - if output: - print(output, end="", flush=True) - if check_exit and rc != 0: - raise TerminalCommandError(f"command failed with rc={rc}: {command}") - return output, rc - - -def run_and_print_quiet_raw( - term: Terminal, - command: str, - quiet_seconds: float = 1.0, - max_duration: float = 30.0, -) -> str: - # For non-shell environments (e.g. U-Boot), do not append shell-style - # status markers; just send and wait for output to go quiet. - output = term.send_until_quiet( - command, - quiet_seconds=quiet_seconds, - max_duration=max_duration, - ) - if output: - print(output, end="", flush=True) - return output - - -def read_and_print_until_quiet( - term: Terminal, - quiet_seconds: float = 3.0, - max_duration: float = 120.0, -) -> str: - # Read side is decoupled from send side for interactive boot flows. - output = term.read_until_quiet( - quiet_seconds=quiet_seconds, - max_duration=max_duration, - ) - if output: - print(output, end="", flush=True) - return output +def parse_pts(output: str) -> list[int]: + return sorted({int(match) for match in re.findall(r"/dev/pts/(\d+)", output)}) + + +def find_zone1_pts(term: Terminal) -> int: + """List virtio-console pts devices; retry once on failure.""" + for attempt in range(2): + _, pts_output = term.run( + f"zone1_pts_{attempt}", + "ls -1 /dev/pts/[0-9]*", + timeout=15.0, + ) + pts_numbers = parse_pts(pts_output) + if pts_numbers: + return pts_numbers[-1] + if attempt == 0: + time.sleep(2.0) + raise TerminalCommandError("failed to find numeric pts from 'ls -1 /dev/pts/[0-9]*'") + + +def ensure_qemu_terminal(cfg: dict[str, Any], log_path: Path) -> Terminal: + term = cfg.get("_qemu_term") + if term is None: + term = build_terminal(cfg, log_path) + term.open() + cfg["_qemu_term"] = term + cfg["_zone0_log_path"] = log_path + return term + + +def close_qemu_terminal(cfg: dict[str, Any]) -> None: + term = cfg.get("_qemu_term") + if term is not None: + term.close() + cfg["_qemu_term"] = None + + +def close_board_terminal(cfg: dict[str, Any]) -> None: + term = cfg.get("_board_term") + if term is not None: + term.close() + cfg["_board_term"] = None + + +def get_active_terminal(cfg: dict[str, Any]) -> Terminal | None: + return cfg.get("_qemu_term") or cfg.get("_board_term") -def run_and_print_send_only( - term: Terminal, - command: str, - read_duration: float = 0.5, -) -> str: - # For commands that switch interactive context (e.g. screen attach), - # only send and collect a short best-effort echo. - output = term.send_and_drain(command, read_duration=read_duration) - if output: - print(output, end="", flush=True) - return output +def close_active_terminal(cfg: dict[str, Any]) -> None: + close_qemu_terminal(cfg) + close_board_terminal(cfg) + + +def board_power_script(cfg: dict[str, Any]) -> Path: + return cfg["workspace"] / "jenkins" / "board_power.sh" + + +def board_power_cycle(cfg: dict[str, Any]) -> None: + power_port = str(cfg.get("power_serial", "")).strip() + if not power_port: + return + script = board_power_script(cfg) + if not script.is_file(): + raise SystemExit(f"board power script not found: {script}") + subprocess.run(["bash", str(script), "cycle", power_port], check=True, cwd=cfg["workspace"]) + + +def board_wake_console(term: Terminal, *, repeats: int = 3) -> None: + for _ in range(repeats): + term.send("") + time.sleep(0.2) + + +def board_wait_uboot_prompt(term: Terminal, pattern: str, timeout: float) -> None: + """Wait for U-Boot prompt, periodically waking an idle console.""" + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + remaining = deadline - time.monotonic() + if term.wait_pattern(pattern, timeout=min(5.0, remaining), from_offset=0): + return + board_wake_console(term) + raise TerminalTimeoutError(f"timed out waiting for U-Boot prompt (pattern={pattern!r})") + + +def save_inner_serial_log(cfg: dict[str, Any], content: str) -> None: + if not content: + return + path = logs_dir(cfg) / "zone1_inner_serial.log" + path.write_text(content, encoding="utf-8") def zone0_start(cfg: dict[str, Any], term: Terminal | None) -> int: print("————————————————\ncase: zone0_start\n————————————————\n", flush=True) if cfg["mode"] == "qemu": + # Create log dir before starting QEMU so a permission failure does not + # leave a running guest that must be SIGTERM'd from finally. + log_path = logs_dir(cfg) / "zone0_console.log" + log_path.write_text("", encoding="utf-8") + cmd = ["make", f"ARCH={cfg['arch']}", f"BOARD={cfg['board']}", "MODE=release", "ci-run"] proc = subprocess.Popen(cmd, cwd=cfg["workspace"], start_new_session=True) cfg["_managed_proc"] = proc cfg["_managed_proc_name"] = "qemu ci-run" wait_qemu_socket(cfg["socket_path"], timeout=30.0) - with build_terminal(cfg) as qemu_term: - bid = cfg["bid"] - if bid == "aarch64/qemu-gicv3": - _ = read_and_print_until_quiet( - qemu_term, - quiet_seconds=3.0, - max_duration=10.0, - ) - qemu_term.send("bootm 0x40400000 - 0x40000000") - if bid == "x86_64/qemu": - time.sleep(10.0) - _ = read_and_print_until_quiet( - qemu_term, - quiet_seconds=5, - max_duration=180.0, - ) + + qemu_term = ensure_qemu_terminal(cfg, log_path) + uboot_cmd = cfg.get("uboot_cmd", "") + uboot_ready = cfg.get("uboot_ready_pattern", "") + if uboot_cmd: + if not uboot_ready: + uboot_ready = r"*=>" + if not qemu_term.wait_pattern(uboot_ready, timeout=10.0): + raise TerminalTimeoutError("timed out waiting for U-Boot prompt") + qemu_term.send(uboot_cmd) + if not qemu_term.wait_pattern(ZONE0_READY_PATTERN, timeout=180.0): + raise TerminalTimeoutError("timed out waiting for zone0 shell prompt") return 0 if cfg["mode"] == "board": - # TODO: reboot board + log_path = logs_dir(cfg) / "zone0_console.log" + log_path.write_text("", encoding="utf-8") + + board_term = build_terminal(cfg, log_path) + board_term.open() + cfg["_board_term"] = board_term + board_power_cycle(cfg) + time.sleep(3.0) + board_wake_console(board_term) + + uboot_cmd = cfg.get("uboot_cmd", "") + uboot_ready = cfg.get("uboot_ready_pattern", "") + if uboot_cmd: + if not uboot_ready: + uboot_ready = r"=>" + board_wait_uboot_prompt(board_term, uboot_ready, timeout=10.0) + time.sleep(0.3) + board_term.send(uboot_cmd) + if not board_term.wait_pattern(ZONE0_READY_PATTERN, timeout=180.0): + raise TerminalTimeoutError("timed out waiting for zone0 shell prompt") + return 0 + return 0 + + +def lspci_expected_cfg(cfg: dict[str, Any]) -> dict[str, Any]: + raw = cfg.get("lspci") or {} + if not isinstance(raw, dict): + raw = {} + expected_bdfs = [str(x) for x in raw.get("expected_bdfs") or []] + min_count = int(raw.get("min_count", len(expected_bdfs) or 1)) + return {"expected_bdfs": expected_bdfs, "min_count": min_count} + + +def save_lspci_artifacts(cfg: dict[str, Any], name: str, content: str) -> None: + path = logs_dir(cfg) / name + path.write_text(content, encoding="utf-8") + print(f"[lspci] saved {path}", flush=True) + + +def lspci(cfg: dict[str, Any], term: Terminal | None) -> int: + print("————————————————\ncase: lspci\n————————————————\n", flush=True) + if term is None: + raise SystemExit("terminal backend is required (run zone0_start first)") + + expected = lspci_expected_cfg(cfg) + + _, output = term.run( + "lspci", + "timeout 20 lspci -D > /tmp/ci_lspci.log 2>&1; cat /tmp/ci_lspci.log", + timeout=60.0, + ) + save_lspci_artifacts(cfg, "lspci.log", f"=== lspci -D ===\n{output}\n") + + matched = [bdf for bdf in expected["expected_bdfs"] if bdf in output] + missing = [bdf for bdf in expected["expected_bdfs"] if bdf not in output] + line_count = len([line for line in output.splitlines() if line.strip()]) + + print(f"[lspci] devices listed: {line_count}", flush=True) + if expected["expected_bdfs"]: + print( + f"[lspci] matched expected BDFs ({len(matched)}/{len(expected['expected_bdfs'])}): {matched}", + flush=True, + ) + if missing: + print(f"[lspci] missing expected BDFs: {missing}", flush=True) + + if expected["expected_bdfs"]: + if len(matched) < expected["min_count"]: + raise TerminalCommandError( + "lspci found " + f"{len(matched)}/{expected['min_count']} expected devices; " + f"missing: {missing}" + ) + elif line_count < expected["min_count"]: + raise TerminalCommandError( + f"lspci listed {line_count} devices, expected at least {expected['min_count']}" + ) + + print("lspci verification passed", flush=True) + return 0 + + +def ping_success(output: str) -> bool: + if re.search(r"\b0% (?:packet )?loss\b", output): + return True + match = re.search(r"(\d+) packets? received", output) + return bool(match and int(match.group(1)) > 0) + + +def network(cfg: dict[str, Any], term: Terminal | None) -> int: + print("————————————————\ncase: network\n————————————————\n", flush=True) + if cfg["mode"] != "board": + print("[network] skipped (not board mode)", flush=True) return 0 + if term is None: + raise SystemExit("terminal backend is required (run zone0_start first)") + + host_ip = cfg["network_host_ip"] + ping_count = cfg["network_ping_count"] + _, output = term.run( + "network_ping", + f"ping -c {ping_count} -W 5 {host_ip}", + timeout=30.0, + ) + save_lspci_artifacts(cfg, "network_ping.log", f"=== ping {host_ip} ===\n{output}\n") + + if not ping_success(output): + raise TerminalCommandError(f"ping {host_ip} failed") + + print(f"[network] ping {host_ip} ok, staging files on host", flush=True) + script = cfg["workspace"] / "jenkins" / "board_scp.sh" + if not script.is_file(): + raise SystemExit(f"board scp script not found: {script}") + + env = os.environ.copy() + env["ARCH"] = cfg["arch"] + env["BOARD"] = cfg["board"] + if cfg["kdir"]: + env["KDIR"] = cfg["kdir"] + env["WORKSPACE_ROOT"] = str(cfg["workspace"]) + env["HVISOR_TOOL_PATH"] = cfg["hvisor_tool_path"] + env["STAGING_DIR"] = cfg["network_staging_dir"] + if cfg.get("zone1_dtb"): + zone1_dtb = Path(cfg["zone1_dtb"]) + if not zone1_dtb.is_absolute(): + zone1_dtb = cfg["workspace"] / zone1_dtb + env["ZONE1_DTB"] = str(zone1_dtb.resolve()) + + subprocess.run(["bash", str(script)], check=True, cwd=cfg["workspace"], env=env) + + # Let host staging finish and drain any buffered serial output before scp. + time.sleep(3.0) + + host_user = cfg["network_host_user"] + staging_dir = cfg["network_staging_dir"] + term.run( + "network_scp_env", + f"export CI_H={host_ip} CI_U={host_user} CI_D={staging_dir}", + timeout=30.0, + ) + # Keep the scp line short: serial consoles truncate long commands. + scp_cmd = "scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $CI_U@$CI_H:$CI_D/* /root/" + print(f"[network] pulling staged files from {host_user}@{host_ip}:{staging_dir}", flush=True) + pull_rc, _ = term.run("network_scp_pull", scp_cmd, timeout=300.0) + if pull_rc != 0: + raise TerminalCommandError(f"board scp pull failed with rc={pull_rc}") + + term.run( + "network_chmod", + "chmod +x /root/boot_zone1.sh /root/check_serial.sh 2>/dev/null || true", + timeout=60.0, + ) + print("network test and file deploy passed", flush=True) return 0 @@ -162,36 +341,43 @@ def zone1_start(cfg: dict[str, Any], term: Terminal | None) -> int: print("————————————————\ncase: zone1_start\n————————————————\n", flush=True) if term is None: raise SystemExit("terminal backend is required") - # _ = run_and_print_quiet_raw(term, "bash", quiet_seconds=1.0, max_duration=15.0) - _, _ = run_and_print_quiet(term, "cd /root", quiet_seconds=1.0, max_duration=15.0) - _, _ = run_and_print_quiet(term, "ls", quiet_seconds=1.0, max_duration=15.0) - _, _ = run_and_print_quiet(term, "cat boot_zone1.sh", quiet_seconds=1.0, max_duration=15.0) - _, boot_rc = run_and_print_quiet( - term, - "./boot_zone1.sh", - quiet_seconds=15, - max_duration=30.0, + + inner_log = "/tmp/zone1_inner.log" + _, _ = term.run("zone1_cd", "cd /root", timeout=15.0) + _, _ = term.run("zone1_ls", "ls", timeout=15.0) + _, _ = term.run("zone1_cat_boot", "cat boot_zone1.sh", timeout=15.0) + boot_rc, _ = term.run("zone1_boot", "./boot_zone1.sh", timeout=120.0) + _, _ = term.run("zone1_list", "./hvisor zone list", timeout=15.0) + + max_pts = find_zone1_pts(term) + + check_rc, _ = term.run( + "zone1_serial_check", + f"./check_serial.sh /dev/pts/{max_pts} {inner_log} {int(ZONE1_INNER_PROMPT_TIMEOUT)}", + timeout=ZONE1_INNER_PROMPT_TIMEOUT + 30.0, ) - _, _ = run_and_print_quiet(term, "./hvisor zone list", quiet_seconds=1.0, max_duration=15.0) - if cfg["arch"] != "x86_64": - _ = run_and_print_quiet_raw(term, "script /dev/null", quiet_seconds=1.0, max_duration=15.0) - pts_output, _ = run_and_print_quiet(term, "ls -1 /dev/pts/[0-9]*", quiet_seconds=1.0, max_duration=15.0) - pts_numbers = sorted(int(match) for match in re.findall(r"/dev/pts/(\d+)", pts_output)) - if not pts_numbers: - raise TerminalCommandError("failed to find numeric pts from 'ls -1 /dev/pts/[0-9]*'") - max_pts = pts_numbers[-1] - _ = run_and_print_send_only(term, f"screen /dev/pts/{max_pts}", read_duration=20.0) - _ = run_and_print_send_only(term, "\n", read_duration=2.0) - _, _ = run_and_print_quiet(term, "ls", quiet_seconds=1.0, max_duration=15.0) + # Fetch via tail to limit serial traffic; generous timeout for slow Jenkins consoles. + _, inner_output = term.run( + "zone1_inner_log", + f"tail -c 131072 {inner_log}", + timeout=ZONE1_INNER_LOG_FETCH_TIMEOUT, + ) + save_inner_serial_log(cfg, inner_output) + if boot_rc != 0: - raise TerminalCommandError(f"command failed with rc={boot_rc}: sh ./boot_zone1.sh") - else: - print("zone1_started successfully", flush=True) + raise TerminalCommandError(f"command failed with rc={boot_rc}: ./boot_zone1.sh") + if check_rc != 0: + raise TerminalCommandError( + f"command failed with rc={check_rc}: check_serial.sh (no console prompt)" + ) + print("zone1_started successfully", flush=True) return 0 CASE_HANDLERS: dict[str, CaseFunc] = { "zone0_start": zone0_start, + "network": network, + "lspci": lspci, "zone1_start": zone1_start, } @@ -218,23 +404,47 @@ def load_runtime_config(args: argparse.Namespace) -> dict[str, Any]: if not cases: raise SystemExit(f"no test cases configured for bid '{args.bid}'") + cell_root = Path.cwd() + build_args = bid_entry.get("build_args") or {} + network_cfg = tests.get("network") or {} + if not isinstance(network_cfg, dict): + network_cfg = {} + hvisor_tool_path = os.environ.get("HVISOR_TOOL_PATH", "").strip() + if not hvisor_tool_path: + hvisor_tool_path = str((cell_root / "hvisor-tool").resolve()) + elif not Path(hvisor_tool_path).is_absolute(): + hvisor_tool_path = str((cell_root / hvisor_tool_path).resolve()) return { "bid": args.bid, "arch": arch, "board": board, "mode": mode, "cases": cases, - "workspace": Path(__file__).resolve().parent.parent, - "socket_path": str((Path(__file__).resolve().parent.parent / ".qemu" / "qemu.sock").resolve()), + "workspace": cell_root, + "kdir": str(build_args.get("KDIR", "")).strip(), + "hvisor_tool_path": hvisor_tool_path, + "socket_path": str((cell_root / ".qemu" / "qemu.sock").resolve()), "serial_port": str(tests.get("serial", "/dev/null")), + "power_serial": str(tests.get("power_serial", "")).strip(), "baudrate": int(tests.get("baudrate", 1500000)), + "uboot_cmd": str(tests.get("uboot_cmd", "")).strip(), + "uboot_ready_pattern": str(tests.get("uboot_ready_pattern", "")).strip(), + "tftp_dir": str(tests.get("tftp_dir", "/home/light/tftp")).strip(), + "lspci": tests.get("lspci") or {}, + "network_host_ip": str(network_cfg.get("host_ip", "192.168.1.181")).strip(), + "network_host_user": str(network_cfg.get("host_user", "light")).strip(), + "network_staging_dir": str( + network_cfg.get("staging_dir", "/home/light/tftp/ci_deploy") + ).strip(), + "network_ping_count": int(network_cfg.get("ping_count", 3)), + "zone1_dtb": str(network_cfg.get("zone1_dtb", "")).strip(), } -def build_terminal(cfg: dict[str, Any]) -> Terminal: +def build_terminal(cfg: dict[str, Any], log_path: Path) -> Terminal: if cfg["mode"] == "qemu": - return Terminal.from_qemu_socket(path=cfg["socket_path"]) - return Terminal.from_serial(port=cfg["serial_port"], baudrate=cfg["baudrate"]) + return Terminal.from_qemu_socket(path=cfg["socket_path"], log_path=log_path) + return Terminal.from_serial(port=cfg["serial_port"], baudrate=cfg["baudrate"], log_path=log_path) def main() -> int: @@ -253,8 +463,20 @@ def main() -> int: return rc time.sleep(5.0) continue - - with build_terminal(cfg) as term: + + term = get_active_terminal(cfg) + if term is None: + log_path = logs_dir(cfg) / "zone1_console.log" + log_path.write_text("", encoding="utf-8") + with build_terminal(cfg, log_path) as term: + try: + rc = case_fn(cfg, term) + except (TerminalTimeoutError, TerminalCommandError) as exc: + print(f"[ci_runner] terminal command failed in case '{case_name}': {exc}", flush=True) + return 1 + if rc != 0: + return rc + else: try: rc = case_fn(cfg, term) except (TerminalTimeoutError, TerminalCommandError) as exc: @@ -262,9 +484,10 @@ def main() -> int: return 1 if rc != 0: return rc - time.sleep(5.0) + time.sleep(5.0) return 0 finally: + close_active_terminal(cfg) terminate_managed_process(cfg) diff --git a/jenkins/prepare.sh b/jenkins/prepare.sh old mode 100644 new mode 100755 index ad0f3d1a9..4e2f71e16 --- a/jenkins/prepare.sh +++ b/jenkins/prepare.sh @@ -106,9 +106,34 @@ fi cp "${ZONE1_BOOT_SCRIPT}" "${ROOTFS_DIR}/root/" +CHECK_SERIAL_SCRIPT="${WORKSPACE_ROOT}/jenkins/check_serial.sh" +if [ -f "${CHECK_SERIAL_SCRIPT}" ]; then + cp "${CHECK_SERIAL_SCRIPT}" "${ROOTFS_DIR}/root/" + chmod +x "${ROOTFS_DIR}/root/check_serial.sh" +fi + if [ -f "${ROOTFS_DIR}/root/boot_zone1.sh" ]; then chmod +x "${ROOTFS_DIR}/root/boot_zone1.sh" fi if [ -f "${ROOTFS_DIR}/root/screen_zone1.sh" ]; then chmod +x "${ROOTFS_DIR}/root/screen_zone1.sh" fi + +ROOTFS2_IMG="" +if [ -f "${VIRTDISK_DIR}/rootfs2.ext4" ]; then + ROOTFS2_IMG="${VIRTDISK_DIR}/rootfs2.ext4" +elif [ -f "${VIRTDISK_DIR}/rootfs2.img" ]; then + ROOTFS2_IMG="${VIRTDISK_DIR}/rootfs2.img" +fi + +if [ -n "${ROOTFS2_IMG}" ]; then + ROOTFS2_MNT="${VIRTDISK_DIR}/rootfs2" + mkdir -p "${ROOTFS2_MNT}" + mount -o loop "${ROOTFS2_IMG}" "${ROOTFS2_MNT}" + if [ -f "${ROOTFS2_MNT}/init" ]; then + chmod +x "${ROOTFS2_MNT}/init" + fi + umount "${ROOTFS2_MNT}" + rmdir "${ROOTFS2_MNT}" + cp "${ROOTFS2_IMG}" "${ROOTFS_DIR}/root/" +fi diff --git a/jenkins/terminal.py b/jenkins/terminal.py index 33a96c4aa..b31ee4f88 100644 --- a/jenkins/terminal.py +++ b/jenkins/terminal.py @@ -3,16 +3,23 @@ from __future__ import annotations +import queue +import re import select import socket +import threading import time import uuid -import re from abc import ABC, abstractmethod from dataclasses import dataclass +from pathlib import Path import serial +RUN_RESULT_RE = re.compile( + r"__CI_RUN_RESULT__ case=(?P[^\s]+) run_id=(?P[a-f0-9]+) rc=(?P\d+)" +) + class TerminalTimeoutError(TimeoutError): """Raised when terminal command wait times out.""" @@ -41,6 +48,10 @@ def read(self, max_bytes: int = 4096) -> bytes: def write(self, data: bytes) -> None: pass + @abstractmethod + def flush_input(self) -> None: + pass + @dataclass class QemuSocketBackend(TerminalBackend): @@ -80,6 +91,14 @@ def write(self, data: bytes) -> None: raise RuntimeError("QEMU socket is not open") self._sock.sendall(data) + def flush_input(self) -> None: + if self._sock is None: + return + while True: + chunk = self.read() + if not chunk: + break + @dataclass class SerialBackend(TerminalBackend): @@ -97,6 +116,9 @@ def open(self) -> None: timeout=self.timeout, write_timeout=self.timeout, ) + # CH340 adapters often need DTR asserted before the target UART TX is enabled. + self._serial.dtr = True + self._serial.rts = False def close(self) -> None: if self._serial is None: @@ -115,47 +137,166 @@ def write(self, data: bytes) -> None: self._serial.write(data) self._serial.flush() + def flush_input(self) -> None: + if self._serial is None: + return + self._serial.reset_input_buffer() + + +class LogCollector: + """Background reader that writes terminal output to a log file and console.""" + + def __init__( + self, + backend: TerminalBackend, + log_path: Path, + encoding: str = "utf-8", + console: bool = True, + poll_interval: float = 0.05, + ) -> None: + self.backend = backend + self.log_path = log_path + self.encoding = encoding + self.console = console + self.poll_interval = poll_interval + self._lock = threading.Lock() + self._buffer = "" + self._stop = threading.Event() + self._thread: threading.Thread | None = None + self._console_queue: queue.SimpleQueue[str | None] = queue.SimpleQueue() + self._console_thread: threading.Thread | None = None + + def start(self) -> None: + self.log_path.parent.mkdir(parents=True, exist_ok=True) + if self.console: + self._console_thread = threading.Thread( + target=self._console_loop, name="LogCollectorConsole", daemon=True + ) + self._console_thread.start() + self._thread = threading.Thread(target=self._run_loop, name="LogCollector", daemon=True) + self._thread.start() + + def stop(self) -> None: + self._stop.set() + if self._thread is not None: + self._thread.join(timeout=5.0) + self._thread = None + # Drain any bytes still buffered in the backend after the reader exits. + deadline = time.monotonic() + 1.0 + while time.monotonic() < deadline: + chunk = self.backend.read() + if not chunk: + break + self._append(chunk.decode(self.encoding, errors="replace"), emit_console=False) + if self.console: + self._console_queue.put(None) + if self._console_thread is not None: + self._console_thread.join(timeout=5.0) + self._console_thread = None + + def offset(self) -> int: + with self._lock: + return len(self._buffer) + + def tail_since(self, offset: int) -> str: + with self._lock: + if offset < 0 or offset > len(self._buffer): + return self._buffer + return self._buffer[offset:] + + def text(self) -> str: + with self._lock: + return self._buffer + + def _append(self, chunk: str, *, emit_console: bool = True) -> None: + if not chunk: + return + with self._lock: + self._buffer += chunk + try: + with self.log_path.open("a", encoding=self.encoding) as fh: + fh.write(chunk) + except OSError: + pass + if emit_console and self.console: + self._console_queue.put(chunk) + + def _console_loop(self) -> None: + while True: + chunk = self._console_queue.get() + if chunk is None: + return + print(chunk, end="", flush=True) + + def _run_loop(self) -> None: + while not self._stop.is_set(): + chunk = self.backend.read() + if chunk: + self._append(chunk.decode(self.encoding, errors="replace")) + continue + time.sleep(self.poll_interval) + class Terminal: - """High level terminal wrapper with command helpers.""" + """High level terminal wrapper with log-file-driven command helpers.""" - def __init__(self, backend: TerminalBackend, encoding: str = "utf-8") -> None: + def __init__( + self, + backend: TerminalBackend, + log_path: Path, + encoding: str = "utf-8", + console: bool = True, + ) -> None: self.backend = backend self.encoding = encoding + self._collector = LogCollector(backend, log_path, encoding=encoding, console=console) self._opened = False @classmethod def from_qemu_socket( cls, path: str, + log_path: Path, connect_timeout: float = 10.0, io_timeout: float = 0.2, encoding: str = "utf-8", + console: bool = True, ) -> "Terminal": return cls( QemuSocketBackend(path=path, connect_timeout=connect_timeout, io_timeout=io_timeout), + log_path=log_path, encoding=encoding, + console=console, ) @classmethod def from_serial( cls, port: str, + log_path: Path, baudrate: int = 115200, timeout: float = 0.2, encoding: str = "utf-8", + console: bool = True, ) -> "Terminal": - return cls(SerialBackend(port=port, baudrate=baudrate, timeout=timeout), encoding=encoding) + return cls( + SerialBackend(port=port, baudrate=baudrate, timeout=timeout), + log_path=log_path, + encoding=encoding, + console=console, + ) def open(self) -> None: if self._opened: return self.backend.open() + self._collector.start() self._opened = True def close(self) -> None: if not self._opened: return + self._collector.stop() self.backend.close() self._opened = False @@ -166,190 +307,65 @@ def __enter__(self) -> "Terminal": def __exit__(self, exc_type, exc, tb) -> None: self.close() + def flush_input(self) -> None: + self._ensure_open() + self.backend.flush_input() + def send(self, command: str) -> None: self._ensure_open() payload = command.rstrip("\n") + "\n" self.backend.write(payload.encode(self.encoding, errors="replace")) - def read_for( - self, - duration: float = 2.0, - poll_interval: float = 0.05, - ) -> str: - self._ensure_open() - deadline = time.monotonic() + duration - chunks: list[str] = [] - while time.monotonic() < deadline: - chunk = self.backend.read() - if chunk: - chunks.append(chunk.decode(self.encoding, errors="replace")) - continue - time.sleep(poll_interval) - return "".join(chunks) - - def send_until_get( + def run( self, + case: str, command: str, timeout: float = 30.0, poll_interval: float = 0.05, - include_marker_line: bool = False, - ) -> str: + ) -> tuple[int, str]: + """Run a shell command and wait for __CI_RUN_RESULT__ in the log.""" self._ensure_open() - marker = f"__HV_TERMINAL_DONE_{uuid.uuid4().hex}__" - self.send(f"{command}; echo {marker}") + run_id = uuid.uuid4().hex + offset = self._collector.offset() + wrapped = f"{command}; echo __CI_RUN_RESULT__ case={case} run_id={run_id} rc=$?" + self.send(wrapped) deadline = time.monotonic() + timeout - buf = "" + run_id_needle = f"run_id={run_id}" while time.monotonic() < deadline: - chunk = self.backend.read() - if chunk: - buf += chunk.decode(self.encoding, errors="replace") - if marker in buf: - if include_marker_line: - return buf - return self._trim_after_marker(buf, marker) + chunk = self._collector.tail_since(offset) + if run_id_needle in chunk: + matches = list(RUN_RESULT_RE.finditer(chunk)) + for match in reversed(matches): + if match.group("run_id") == run_id: + rc = int(match.group("rc")) + output = chunk[: match.start()] + return rc, output time.sleep(poll_interval) - raise TerminalTimeoutError(f"timed out waiting for terminal marker: {marker}") - def send_until_quiet( - self, - command: str, - quiet_seconds: float = 1.0, - max_duration: float = 30.0, - poll_interval: float = 0.05, - ) -> str: - self._ensure_open() - self.send(command) - - start = time.monotonic() - deadline = start + max_duration - last_output_at = start - buf = "" - - while True: - now = time.monotonic() - if now >= deadline: - raise TerminalTimeoutError( - f"timed out waiting for terminal quiet period after command: {command}" - ) - - chunk = self.backend.read() - if chunk: - buf += chunk.decode(self.encoding, errors="replace") - last_output_at = time.monotonic() - continue - - if (now - last_output_at) >= quiet_seconds: - return buf - time.sleep(poll_interval) - - def send_and_drain( - self, - command: str, - read_duration: float = 0.5, - poll_interval: float = 0.05, - ) -> str: - """Send command and collect best-effort output for a fixed duration.""" - self._ensure_open() - self.send(command) - - deadline = time.monotonic() + read_duration - buf = "" - while time.monotonic() < deadline: - chunk = self.backend.read() - if chunk: - buf += chunk.decode(self.encoding, errors="replace") - continue - time.sleep(poll_interval) - return buf - - def read_until_quiet( - self, - quiet_seconds: float = 3.0, - max_duration: float = 120.0, - poll_interval: float = 0.05, - ) -> str: - """Continuously read until quiet for x seconds or total timeout.""" - self._ensure_open() - start = time.monotonic() - deadline = start + max_duration - last_output_at = start - buf = "" - - while time.monotonic() < deadline: - chunk = self.backend.read() - if chunk: - buf += chunk.decode(self.encoding, errors="replace") - last_output_at = time.monotonic() - continue - - now = time.monotonic() - if (now - last_output_at) >= quiet_seconds: - return buf - time.sleep(poll_interval) - return buf + raise TerminalTimeoutError( + f"timed out waiting for run result (case={case}, run_id={run_id}): {command}" + ) - def run_until_quiet_with_status( + def wait_pattern( self, - command: str, - quiet_seconds: float = 1.0, - max_duration: float = 30.0, + pattern: str, + timeout: float = 120.0, poll_interval: float = 0.05, - ) -> tuple[str, int]: - marker = f"__HV_TERMINAL_RC_{uuid.uuid4().hex}__" - wrapped = f"{command}; echo {marker}0" + from_offset: int | None = None, + ) -> bool: + """Wait until regex pattern appears in the collected log.""" self._ensure_open() - self.send(wrapped) - - deadline = time.monotonic() + max_duration - buf = "" - marker_pattern = re.compile(re.escape(marker) + r"(\d+)") - rc = -1 - marker_seen_at = 0.0 - + offset = self._collector.offset() if from_offset is None else from_offset + compiled = re.compile(pattern) + deadline = time.monotonic() + timeout while time.monotonic() < deadline: - chunk = self.backend.read() - if chunk: - buf += chunk.decode(self.encoding, errors="replace") - matches = list(marker_pattern.finditer(buf)) - if matches: - last = matches[-1] - rc = int(last.group(1)) - if marker_seen_at <= 0.0: - marker_seen_at = time.monotonic() - continue - - if marker_seen_at > 0.0 and (time.monotonic() - marker_seen_at) >= quiet_seconds: - cleaned = self._strip_status_marker(buf, marker) - return cleaned, rc - + chunk = self._collector.tail_since(offset) + if compiled.search(chunk): + return True time.sleep(poll_interval) - - raise TerminalTimeoutError(f"timed out waiting for command status marker: {marker}") + return False def _ensure_open(self) -> None: if not self._opened: self.open() - - @staticmethod - def _trim_after_marker(output: str, marker: str) -> str: - idx = output.find(marker) - if idx < 0: - return output - return output[:idx] - - @staticmethod - def _extract_status_marker(output: str, marker: str) -> int: - pattern = re.compile(re.escape(marker) + r"(\d+)") - matches = list(pattern.finditer(output)) - if not matches: - raise TerminalTimeoutError(f"status marker without exit code: {marker}") - return int(matches[-1].group(1)) - - @staticmethod - def _strip_status_marker(output: str, marker: str) -> str: - pattern = re.compile(re.escape(marker) + r"\d+") - matches = list(pattern.finditer(output)) - if not matches: - return output - return output[: matches[-1].start()] diff --git a/platform/aarch64/rk3568/board.rs b/platform/aarch64/rk3568/board.rs index 6b3182d6e..2c76f10e1 100644 --- a/platform/aarch64/rk3568/board.rs +++ b/platform/aarch64/rk3568/board.rs @@ -57,42 +57,81 @@ pub const ROOT_ZONE_CPUS: u64 = (1 << 0) | (1 << 1); pub const ROOT_ZONE_NAME: &str = "root-linux"; pub const ROOT_ZONE_MEMORY_REGIONS: &[HvConfigMemoryRegion] = &[ - // HvConfigMemoryRegion { - // mem_type: MEM_TYPE_IO, - // physical_start: 0x3c0400000, - // virtual_start: 0x3c0400000, - // size: 0x400000, - // }, //pcie - // HvConfigMemoryRegion { - // mem_type: MEM_TYPE_IO, - // physical_start: 0xfe270000, - // virtual_start: 0xfe270000, - // size: 0x10000, - // }, //pcie + // pcie@fe260000 (domain 0) + HvConfigMemoryRegion { + mem_type: MEM_TYPE_IO, + physical_start: 0xf4000000, + virtual_start: 0xf4000000, + size: 0x100000, + }, + HvConfigMemoryRegion { + mem_type: MEM_TYPE_IO, + physical_start: 0xf4100000, + virtual_start: 0xf4100000, + size: 0x100000, + }, + HvConfigMemoryRegion { + mem_type: MEM_TYPE_IO, + physical_start: 0xf4200000, + virtual_start: 0xf4200000, + size: 0x1e00000, + }, + HvConfigMemoryRegion { + mem_type: MEM_TYPE_IO, + physical_start: 0x300000000, + virtual_start: 0x300000000, + size: 0x40000000, + }, + // pcie@fe270000 (domain 1) HvConfigMemoryRegion { mem_type: MEM_TYPE_IO, physical_start: 0xf2000000, virtual_start: 0xf2000000, size: 0x100000, - }, //pcie + }, HvConfigMemoryRegion { mem_type: MEM_TYPE_IO, physical_start: 0xf2100000, virtual_start: 0xf2100000, size: 0x100000, - }, //pcie + }, HvConfigMemoryRegion { mem_type: MEM_TYPE_IO, physical_start: 0xf2200000, virtual_start: 0xf2200000, size: 0x1e00000, - }, //pcie + }, HvConfigMemoryRegion { mem_type: MEM_TYPE_IO, physical_start: 0x340000000, virtual_start: 0x340000000, size: 0x40000000, - }, //pcie + }, + // pcie@fe280000 (domain 2) + HvConfigMemoryRegion { + mem_type: MEM_TYPE_IO, + physical_start: 0xf0000000, + virtual_start: 0xf0000000, + size: 0x100000, + }, + HvConfigMemoryRegion { + mem_type: MEM_TYPE_IO, + physical_start: 0xf0100000, + virtual_start: 0xf0100000, + size: 0x100000, + }, + HvConfigMemoryRegion { + mem_type: MEM_TYPE_IO, + physical_start: 0xf0200000, + virtual_start: 0xf0200000, + size: 0x1e00000, + }, + HvConfigMemoryRegion { + mem_type: MEM_TYPE_IO, + physical_start: 0x380000000, + virtual_start: 0x380000000, + size: 0x40000000, + }, HvConfigMemoryRegion { mem_type: MEM_TYPE_IO, physical_start: 0xfdcb8000, @@ -165,12 +204,12 @@ pub const ROOT_ZONE_MEMORY_REGIONS: &[HvConfigMemoryRegion] = &[ // virtual_start: 0x10f000, // size: 0x1000, // }, //scmi-shmem - HvConfigMemoryRegion { - mem_type: MEM_TYPE_RAM, - physical_start: 0xfd440000, - virtual_start: 0xfd440000, - size: 0x20000, - }, // its + // HvConfigMemoryRegion { + // mem_type: MEM_TYPE_RAM, + // physical_start: 0xfd440000, + // virtual_start: 0xfd440000, + // size: 0x20000, + // }, // its HvConfigMemoryRegion { mem_type: MEM_TYPE_RAM, physical_start: 0x1f0000000, @@ -273,12 +312,24 @@ pub const ROOT_ZONE_MEMORY_REGIONS: &[HvConfigMemoryRegion] = &[ virtual_start: 0xFE310000, size: 0x10000, }, //sdhci + HvConfigMemoryRegion { + mem_type: MEM_TYPE_IO, + physical_start: 0xfe010000, + virtual_start: 0xfe010000, + size: 0x10000, + }, // gmac1 ethernet@fe010000 + HvConfigMemoryRegion { + mem_type: MEM_TYPE_IO, + physical_start: 0xfda00000, + virtual_start: 0xfda00000, + size: 0x200000, + }, // xpcs syscon for gmac RGMII ]; -pub const IRQ_WAKEUP_VIRTIO_DEVICE: usize = 32 + 0x20; +pub const IRQ_WAKEUP_VIRTIO_DEVICE: usize = 32 + 0x26; pub const ROOT_ZONE_IRQS_BITMAP: &[BitmapWord] = &get_irqs_bitmap(&[ - 0x84, 0x98, 0x40, 0x104, 0x105, 0x106, 0x107, 0x2d, 0x2e, 0x2b, 0x2a, 0x29, 0x33, 0x96, 0x11c, - 0x44, 0x43, 0x42, 0x41, 0x8d, + 0x84, 0x98, 0x46, 0x20, 0x1d, 0x104, 0x105, 0x106, 0x107, 0x108, 0x2d, 0x2e, 0x2b, 0x2a, 0x29, + 0x33, 0x96, 0x11c, 0x44, 0x43, 0x42, 0x41, 0x8d, 0x48, 0x49, 0x9d, 0x9e, 0xa2, 0xa3, ]); pub const ROOT_ARCH_ZONE_CONFIG: HvArchZoneConfig = HvArchZoneConfig { @@ -293,22 +344,22 @@ pub const ROOT_ARCH_ZONE_CONFIG: HvArchZoneConfig = HvArchZoneConfig { }), }; pub const ROOT_PCI_CONFIG: &[HvPciConfig] = &[ - // HvPciConfig { - // ecam_base: 0xfe260000, - // ecam_size: 0x400000, - // io_base: 0xf4100000, - // io_size: 0x100000, - // pci_io_base: 0xf4100000, - // mem32_base: 0xf4200000, - // mem32_size: 0x1e00000, - // pci_mem32_base: 0xf4200000, - // mem64_base: 0x300000000, - // mem64_size: 0x40000000, - // pci_mem64_base: 0x300000000, - // bus_range_begin: 0x0, - // bus_range_end: 0x10, - // domain: 0x0, - // }, + HvPciConfig { + ecam_base: 0x3c0000000, + ecam_size: 0x400000, + io_base: 0xf4100000, + io_size: 0x100000, + pci_io_base: 0xf4100000, + mem32_base: 0xf4200000, + mem32_size: 0x1e00000, + pci_mem32_base: 0xf4200000, + mem64_base: 0x300000000, + mem64_size: 0x40000000, + pci_mem64_base: 0x300000000, + bus_range_begin: 0x0, + bus_range_end: 0x10, + domain: 0x0, + }, HvPciConfig { ecam_base: 0x3c0400000, ecam_size: 0x400000, @@ -325,40 +376,68 @@ pub const ROOT_PCI_CONFIG: &[HvPciConfig] = &[ bus_range_end: 0x1f, domain: 0x1, }, - // HvPciConfig { - // ecam_base: 0xfe280000, - // ecam_size: 0x400000, - // io_base: 0xf0100000, - // io_size: 0x100000, - // pci_io_base: 0xf0100000, - // mem32_base: 0xf0200000, - // mem32_size: 0x1e00000, - // pci_mem32_base: 0xf0200000, - // mem64_base: 0x380000000, - // mem64_size: 0x40000000, - // pci_mem64_base: 0x380000000, - // bus_range_begin: 0x20, - // bus_range_end: 0x2f, - // domain: 0x2, - // } + HvPciConfig { + ecam_base: 0x3c0800000, + ecam_size: 0x400000, + io_base: 0xf0100000, + io_size: 0x100000, + pci_io_base: 0xf0100000, + mem32_base: 0xf0200000, + mem32_size: 0x1e00000, + pci_mem32_base: 0xf0200000, + mem64_base: 0x380000000, + mem64_size: 0x40000000, + pci_mem64_base: 0x380000000, + bus_range_begin: 0x20, + bus_range_end: 0x2f, + domain: 0x2, + }, ]; pub const ROOT_ZONE_IVC_CONFIG: [HvIvcConfig; 0] = []; -pub const ROOT_DWC_ATU_CONFIG: &[HvDwcAtuConfig] = &[HvDwcAtuConfig { - ecam_base: 0x3c0400000, - dbi_base: 0x3c0400000, - dbi_size: 0x10000, - apb_base: 0xfe270000, - apb_size: 0x10000, - cfg_base: 0xf2000000, - cfg_size: 0x80000 * 2, - io_cfg_atu_shared: 0, - io_atu_index: 0, - dw_msi_irq: 0, -}]; +pub const ROOT_DWC_ATU_CONFIG: &[HvDwcAtuConfig] = &[ + HvDwcAtuConfig { + ecam_base: 0x3c0000000, + dbi_base: 0x3c0000000, + dbi_size: 0x10000, + apb_base: 0xfe260000, + apb_size: 0x10000, + cfg_base: 0xf4000000, + cfg_size: 0x80000 * 2, + io_cfg_atu_shared: 0, + io_atu_index: 0, + dw_msi_irq: 0x49, // pcie@fe260000 msg irq + }, + HvDwcAtuConfig { + ecam_base: 0x3c0400000, + dbi_base: 0x3c0400000, + dbi_size: 0x10000, + apb_base: 0xfe270000, + apb_size: 0x10000, + cfg_base: 0xf2000000, + cfg_size: 0x80000 * 2, + io_cfg_atu_shared: 0, + io_atu_index: 0, + dw_msi_irq: 0x9e, // pcie@fe270000 msg irq + }, + HvDwcAtuConfig { + ecam_base: 0x3c0800000, + dbi_base: 0x3c0800000, + dbi_size: 0x10000, + apb_base: 0xfe280000, + apb_size: 0x10000, + cfg_base: 0xf0000000, + cfg_size: 0x80000 * 2, + io_cfg_atu_shared: 0, + io_atu_index: 0, + dw_msi_irq: 0xa3, // pcie@fe280000 msg irq + }, +]; -pub const ROOT_PCI_DEVS: [HvPciDevConfig; 2] = [ - pci_dev!(0x0, 0x00, 0x0, 0x0 => 0x00, 0x0, 0x0, VpciDevType::Physical), - pci_dev!(0x0, 0x01, 0x0, 0x0 => 0x01, 0x0, 0x0, VpciDevType::Physical), +pub const ROOT_PCI_DEVS: [HvPciDevConfig; 4] = [ + pci_dev!(0x1, 0x10, 0x0, 0x0 => 0x10, 0x0, 0x0, VpciDevType::Physical), + pci_dev!(0x1, 0x11, 0x0, 0x0 => 0x11, 0x0, 0x0, VpciDevType::Physical), + pci_dev!(0x2, 0x20, 0x0, 0x0 => 0x20, 0x0, 0x0, VpciDevType::Physical), + pci_dev!(0x2, 0x21, 0x0, 0x0 => 0x21, 0x0, 0x0, VpciDevType::Physical), ]; diff --git a/platform/aarch64/rk3568/configs/linux2.json b/platform/aarch64/rk3568/configs/linux2.json index 46d401055..0c64be5bb 100644 --- a/platform/aarch64/rk3568/configs/linux2.json +++ b/platform/aarch64/rk3568/configs/linux2.json @@ -44,5 +44,6 @@ "gicr_size": "0xc0000", "gits_base": "0x0", "gits_size": "0x0" - } + }, + "pci_config":[] } diff --git a/platform/aarch64/rk3568/image/dts/rk3568_limit_zone0.dts b/platform/aarch64/rk3568/image/dts/rk3568_limit_zone0.dts index 420426e81..0eac654e0 100644 --- a/platform/aarch64/rk3568/image/dts/rk3568_limit_zone0.dts +++ b/platform/aarch64/rk3568/image/dts/rk3568_limit_zone0.dts @@ -16,6 +16,7 @@ mmc1 = "/dwmmc@fe2b0000"; mmc2 = "/dwmmc@fe2c0000"; mmc3 = "/dwmmc@fe000000"; + ethernet1 = "/ethernet@fe010000"; }; syscon@fdc50000 { @@ -140,47 +141,47 @@ }; - // pcie@fe260000 { - // power-domains = <0x22 0x0f>; - // #address-cells = <0x03>; - // phy-names = "pcie-phy"; - // bus-range = <0x00 0x0f>; - // clock-names = "aclk_mst\0aclk_slv\0aclk_dbi\0pclk\0aux"; - // reg-names = "pcie-dbi\0pcie-apb"; - // num-ob-windows = <0x02>; - // resets = <0x20 0xa1>; - // interrupts = <0x00 0x4b 0x04 0x00 0x4a 0x04 0x00 0x49 0x04 0x00 0x48 0x04 0x00 0x47 0x04>; - // clocks = <0x20 0x81 0x20 0x82 0x20 0x83 0x20 0x84 0x20 0x85>; - // interrupt-map = <0x00 0x00 0x00 0x01 0xac 0x00 0x00 0x00 0x00 0x02 0xac 0x01 0x00 0x00 0x00 0x03 0xac 0x02 0x00 0x00 0x00 0x04 0xac 0x03>; - // #size-cells = <0x02>; - // max-link-speed = <0x02>; - // device_type = "pci"; - // interrupt-map-mask = <0x00 0x00 0x00 0x07>; - // reset-gpios = <0x83 0x11 0x00>; - // num-lanes = <0x01>; - // compatible = "rockchip,rk3568-pcie\0snps,dw-pcie"; - // ranges = <0x800 0x00 0xf4000000 0x00 0xf4000000 0x00 0x100000 0x81000000 0x00 0xf4100000 0x00 0xf4100000 0x00 0x100000 0x82000000 0x00 0xf4200000 0x00 0xf4200000 0x00 0x1e00000 0xc3000000 0x03 0x00 0x03 0x00 0x00 0x40000000>; - // msi-map = <0x00 0xad 0x00 0x1000>; - // #interrupt-cells = <0x01>; - // status = "okay"; - // interrupt-names = "sys\0pmc\0msg\0legacy\0err"; - // phys = <0x24 0x02>; - // num-viewport = <0x08>; - // reg = <0x03 0xc0000000 0x00 0x400000 0x00 0xfe260000 0x00 0x10000>; - // linux,pci-domain = <0x00>; - // phandle = <0x19d>; - // reset-names = "pipe"; - // num-ib-windows = <0x06>; - - // legacy-interrupt-controller { - // #address-cells = <0x00>; - // interrupts = <0x00 0x48 0x01>; - // interrupt-parent = <0x01>; - // #interrupt-cells = <0x01>; - // phandle = <0xac>; - // interrupt-controller; - // }; - // }; + pcie@fe260000 { + power-domains = <0x22 0x0f>; + #address-cells = <0x03>; + phy-names = "pcie-phy"; + bus-range = <0x00 0x0f>; + clock-names = "aclk_mst\0aclk_slv\0aclk_dbi\0pclk\0aux"; + reg-names = "pcie-dbi\0pcie-apb"; + num-ob-windows = <0x02>; + resets = <0x20 0xa1>; + interrupts = <0x00 0x4b 0x04 0x00 0x4a 0x04 0x00 0x49 0x04 0x00 0x48 0x04 0x00 0x47 0x04>; + clocks = <0x20 0x81 0x20 0x82 0x20 0x83 0x20 0x84 0x20 0x85>; + interrupt-map = <0x00 0x00 0x00 0x01 0xac 0x00 0x00 0x00 0x00 0x02 0xac 0x01 0x00 0x00 0x00 0x03 0xac 0x02 0x00 0x00 0x00 0x04 0xac 0x03>; + #size-cells = <0x02>; + max-link-speed = <0x02>; + device_type = "pci"; + interrupt-map-mask = <0x00 0x00 0x00 0x07>; + reset-gpios = <0x83 0x11 0x00>; + num-lanes = <0x01>; + compatible = "rockchip,rk3568-pcie\0snps,dw-pcie"; + ranges = <0x800 0x00 0xf4000000 0x00 0xf4000000 0x00 0x100000 0x81000000 0x00 0xf4100000 0x00 0xf4100000 0x00 0x100000 0x82000000 0x00 0xf4200000 0x00 0xf4200000 0x00 0x1e00000 0xc3000000 0x03 0x00 0x03 0x00 0x00 0x40000000>; + msi-map = <0x00 0xad 0x00 0x1000>; + #interrupt-cells = <0x01>; + status = "okay"; + interrupt-names = "sys\0pmc\0msg\0legacy\0err"; + phys = <0x24 0x02>; + num-viewport = <0x08>; + reg = <0x03 0xc0000000 0x00 0x400000 0x00 0xfe260000 0x00 0x10000>; + linux,pci-domain = <0x00>; + phandle = <0x19d>; + reset-names = "pipe"; + num-ib-windows = <0x06>; + + legacy-interrupt-controller { + #address-cells = <0x00>; + interrupts = <0x00 0x48 0x01>; + interrupt-parent = <0x01>; + #interrupt-cells = <0x01>; + phandle = <0xac>; + interrupt-controller; + }; + }; syscon@fdcb8000 { compatible = "rockchip,pcie30-phy-grf\0syscon"; @@ -259,48 +260,48 @@ }; }; - // pcie@fe280000 { - // power-domains = <0x22 0x0f>; - // vpcie3v3-supply = <0xb0>; - // #address-cells = <0x03>; - // phy-names = "pcie-phy"; - // bus-range = <0x20 0x2f>; - // clock-names = "aclk_mst\0aclk_slv\0aclk_dbi\0pclk\0aux"; - // reg-names = "pcie-dbi\0pcie-apb"; - // num-ob-windows = <0x02>; - // resets = <0x20 0xc1>; - // interrupts = <0x00 0xa5 0x04 0x00 0xa4 0x04 0x00 0xa3 0x04 0x00 0xa2 0x04 0x00 0xa1 0x04>; - // clocks = <0x20 0x8f 0x20 0x90 0x20 0x91 0x20 0x92 0x20 0x93>; - // interrupt-map = <0x00 0x00 0x00 0x01 0xb1 0x00 0x00 0x00 0x00 0x02 0xb1 0x01 0x00 0x00 0x00 0x03 0xb1 0x02 0x00 0x00 0x00 0x04 0xb1 0x03>; - // #size-cells = <0x02>; - // max-link-speed = <0x03>; - // device_type = "pci"; - // interrupt-map-mask = <0x00 0x00 0x00 0x07>; - // reset-gpios = <0xb2 0x1e 0x00>; - // num-lanes = <0x02>; - // compatible = "rockchip,rk3568-pcie\0snps,dw-pcie"; - // ranges = <0x800 0x00 0xf0000000 0x00 0xf0000000 0x00 0x100000 0x81000000 0x00 0xf0100000 0x00 0xf0100000 0x00 0x100000 0x82000000 0x00 0xf0200000 0x00 0xf0200000 0x00 0x1e00000 0xc3000000 0x03 0x80000000 0x03 0x80000000 0x00 0x40000000>; - // msi-map = <0x2000 0xad 0x2000 0x1000>; - // #interrupt-cells = <0x01>; - // status = "okay"; - // interrupt-names = "sys\0pmc\0msg\0legacy\0err"; - // phys = <0xaf>; - // num-viewport = <0x08>; - // reg = <0x03 0xc0800000 0x00 0x400000 0x00 0xfe280000 0x00 0x10000>; - // linux,pci-domain = <0x02>; - // phandle = <0x19f>; - // reset-names = "pipe"; - // num-ib-windows = <0x06>; - - // legacy-interrupt-controller { - // #address-cells = <0x00>; - // interrupts = <0x00 0xa2 0x01>; - // interrupt-parent = <0x01>; - // #interrupt-cells = <0x01>; - // phandle = <0xb1>; - // interrupt-controller; - // }; - // }; + pcie@fe280000 { + power-domains = <0x22 0x0f>; + vpcie3v3-supply = <0xb0>; + #address-cells = <0x03>; + phy-names = "pcie-phy"; + bus-range = <0x20 0x2f>; + clock-names = "aclk_mst\0aclk_slv\0aclk_dbi\0pclk\0aux"; + reg-names = "pcie-dbi\0pcie-apb"; + num-ob-windows = <0x02>; + resets = <0x20 0xc1>; + interrupts = <0x00 0xa5 0x04 0x00 0xa4 0x04 0x00 0xa3 0x04 0x00 0xa2 0x04 0x00 0xa1 0x04>; + clocks = <0x20 0x8f 0x20 0x90 0x20 0x91 0x20 0x92 0x20 0x93>; + interrupt-map = <0x00 0x00 0x00 0x01 0xb1 0x00 0x00 0x00 0x00 0x02 0xb1 0x01 0x00 0x00 0x00 0x03 0xb1 0x02 0x00 0x00 0x00 0x04 0xb1 0x03>; + #size-cells = <0x02>; + max-link-speed = <0x03>; + device_type = "pci"; + interrupt-map-mask = <0x00 0x00 0x00 0x07>; + reset-gpios = <0xb2 0x1e 0x00>; + num-lanes = <0x02>; + compatible = "rockchip,rk3568-pcie\0snps,dw-pcie"; + ranges = <0x800 0x00 0xf0000000 0x00 0xf0000000 0x00 0x100000 0x81000000 0x00 0xf0100000 0x00 0xf0100000 0x00 0x100000 0x82000000 0x00 0xf0200000 0x00 0xf0200000 0x00 0x1e00000 0xc3000000 0x03 0x80000000 0x03 0x80000000 0x00 0x40000000>; + msi-map = <0x2000 0xad 0x2000 0x1000>; + #interrupt-cells = <0x01>; + status = "okay"; + interrupt-names = "sys\0pmc\0msg\0legacy\0err"; + phys = <0xaf>; + num-viewport = <0x08>; + reg = <0x03 0xc0800000 0x00 0x400000 0x00 0xfe280000 0x00 0x10000>; + linux,pci-domain = <0x02>; + phandle = <0x19f>; + reset-names = "pipe"; + num-ib-windows = <0x06>; + + legacy-interrupt-controller { + #address-cells = <0x00>; + interrupts = <0x00 0xa2 0x01>; + interrupt-parent = <0x01>; + #interrupt-cells = <0x01>; + phandle = <0xb1>; + interrupt-controller; + }; + }; sdhci@fe310000 { clock-names = "core\0bus\0axi\0block\0timer"; @@ -391,7 +392,83 @@ max-frequency = <0x8f0d180>; reset-names = "reset"; }; - + + syscon@fda00000 { + compatible = "rockchip,rk3568-xpcs\0syscon"; + status = "okay"; + reg = <0x00 0xfda00000 0x00 0x200000>; + phandle = <0x161>; + }; + + ethernet@fe010000 { + pinctrl-names = "default"; + phy-mode = "rgmii"; + snps,mixed-burst; + snps,mtl-rx-config = <0x81>; + snps,reset-active-low; + pinctrl-0 = <0x84 0x85 0x86 0x87 0x88>; + clock-names = "stmmaceth\0mac_clk_rx\0mac_clk_tx\0clk_mac_refout\0aclk_mac\0pclk_mac\0clk_mac_speed\0ptp_ref\0pclk_xpcs\0clk_xpcs_eee"; + assigned-clocks = <0x20 0x189 0x20 0x186>; + assigned-clock-parents = <0x20 0x187 0x20 0xc5>; + snps,mtl-tx-config = <0x82>; + local-mac-address = [2a fd 45 b0 3b 1a]; + assigned-clock-rates = <0x00 0x7735940>; + resets = <0x20 0xec>; + interrupts = <0x00 0x20 0x04 0x00 0x1d 0x04>; + clocks = <0x20 0x186 0x20 0x189 0x20 0x189 0x20 0xc7 0x20 0xc3 0x20 0xc4 0x20 0x189 0x20 0xc8 0x20 0xac 0x20 0xab>; + clock_in_out = "output"; + snps,tso; + compatible = "rockchip,rk3568-gmac\0snps,dwmac-4.20a"; + status = "okay"; + rockchip,grf = <0x34>; + interrupt-names = "macirq\0eth_wake_irq"; + snps,reset-gpio = <0x83 0x08 0x01>; + reg = <0x00 0xfe010000 0x00 0x10000>; + rx_delay = <0x26>; + phandle = <0x7f>; + phy-handle = <0x89>; + reset-names = "stmmaceth"; + tx_delay = <0x4f>; + snps,axi-config = <0x80>; + snps,reset-delays-us = <0x00 0x4e20 0x186a0>; + + mdio { + #address-cells = <0x01>; + #size-cells = <0x00>; + compatible = "snps,dwmac-mdio"; + phandle = <0x18a>; + + phy@0 { + compatible = "ethernet-phy-ieee802.3-c22"; + reg = <0x00>; + phandle = <0x89>; + }; + }; + + tx-queues-config { + phandle = <0x82>; + snps,tx-queues-to-use = <0x01>; + + queue0 { + }; + }; + + stmmac-axi-config { + snps,wr_osr_lmt = <0x04>; + phandle = <0x80>; + snps,blen = <0x00 0x00 0x00 0x00 0x10 0x08 0x04>; + snps,rd_osr_lmt = <0x08>; + }; + + rx-queues-config { + snps,rx-queues-to-use = <0x01>; + phandle = <0x81>; + + queue0 { + }; + }; + }; + memory { device_type = "memory"; reg = <0x00 0x200000 0x00 0x8200000 0x00 0x9400000 0x00 0xe6c00000 0x01 0xf0000000 0x00 0x10000000>; @@ -652,13 +729,13 @@ phandle = <0x01>; interrupt-controller; - // interrupt-controller@fd440000 { - // msi-controller; - // compatible = "arm,gic-v3-its"; - // reg = <0x00 0xfd440000 0x00 0x20000>; - // phandle = <0xad>; - // #msi-cells = <0x01>; - // }; + interrupt-controller@fd440000 { + msi-controller; + compatible = "arm,gic-v3-its"; + reg = <0x00 0xfd440000 0x00 0x20000>; + phandle = <0xad>; + #msi-cells = <0x01>; + }; }; @@ -1650,6 +1727,6 @@ hvisor_virtio_device { compatible = "hvisor"; interrupt-parent = <0x01>; - interrupts = <0x00 0x20 0x01>; + interrupts = <0x00 0x26 0x01>; }; -}; \ No newline at end of file +}; diff --git a/platform/aarch64/rk3568/kconfig/defconfig b/platform/aarch64/rk3568/kconfig/defconfig index 212ee3ba3..04cc741de 100644 --- a/platform/aarch64/rk3568/kconfig/defconfig +++ b/platform/aarch64/rk3568/kconfig/defconfig @@ -4,3 +4,5 @@ CONFIG_NO_PCIE_BAR_REALLOC=y CONFIG_PCI=y CONFIG_PCIE_DWC=y CONFIG_UART_16550=y +CONFIG_PCI_INIT_DELAY=y + diff --git a/platform/aarch64/rk3568/scripts/boot_zone1.sh b/platform/aarch64/rk3568/scripts/boot_zone1.sh index 6de678558..b881d4954 100644 --- a/platform/aarch64/rk3568/scripts/boot_zone1.sh +++ b/platform/aarch64/rk3568/scripts/boot_zone1.sh @@ -7,5 +7,4 @@ rm nohup.out mkdir -p /dev/pts mount -t devpts devpts /dev/pts nohup ./hvisor virtio start virtio.json & -./hvisor zone start linux2.json && \ -cat nohup.out | grep "char device" \ No newline at end of file +./hvisor zone start linux2.json \ No newline at end of file diff --git a/platform/aarch64/rk3568/scripts/uboot_cmd b/platform/aarch64/rk3568/scripts/uboot_cmd index 731ecc3a9..cbbb1f06b 100644 --- a/platform/aarch64/rk3568/scripts/uboot_cmd +++ b/platform/aarch64/rk3568/scripts/uboot_cmd @@ -1 +1 @@ -pci enum;setenv serverip 192.168.0.1; setenv ipaddr 192.168.0.2; setenv loadaddr 0x60800000; setenv fdt_addr 0xa0000000; setenv zone0_kernel_addr 0x00280000; tftp ${loadaddr} ${serverip}:hvisor.bin; tftp ${fdt_addr} ${serverip}:rk3568_limit_zone0.dtb; tftp ${zone0_kernel_addr} ${serverip}:Image_test5; bootm ${loadaddr} - ${fdt_addr}; +pci enum;setenv serverip 192.168.1.181; setenv ipaddr 192.168.1.240; setenv loadaddr 0x60800000; setenv fdt_addr 0xa0000000; setenv zone0_kernel_addr 0x00280000; tftp ${loadaddr} ${serverip}:hvisor.bin; tftp ${fdt_addr} ${serverip}:rk3568_limit_zone0.dtb; tftp ${zone0_kernel_addr} ${serverip}:Image; bootm ${loadaddr} - ${fdt_addr}; diff --git a/platform/loongarch64/ls3a5000/kconfig/defconfig b/platform/loongarch64/ls3a5000/kconfig/defconfig index a7f13f67a..c061a0fd3 100644 --- a/platform/loongarch64/ls3a5000/kconfig/defconfig +++ b/platform/loongarch64/ls3a5000/kconfig/defconfig @@ -4,4 +4,4 @@ CONFIG_LOONGSON_7A2000=y CONFIG_LOONGSON_UART=y CONFIG_NO_PCIE_BAR_REALLOC=y CONFIG_PCI=y -CONFIG_PCIE_LOONGARCH64=y \ No newline at end of file +CONFIG_PCIE_LOONGARCH64=y diff --git a/platform/riscv64/qemu-plic/configs/virtio-backend.json b/platform/riscv64/qemu-plic/configs/virtio-backend.json index 7ab478162..5ce9f9bea 100644 --- a/platform/riscv64/qemu-plic/configs/virtio-backend.json +++ b/platform/riscv64/qemu-plic/configs/virtio-backend.json @@ -10,14 +10,22 @@ } ], "devices": [ - { - "type": "console", - "addr": "0x10007000", - "len": "0x1000", - "irq": 7, - "status": "enable" - } - ] + { + "type": "blk", + "addr": "0x10006000", + "len": "0x1000", + "irq": 6, + "img": "rootfs2.ext4", + "status": "enable" + }, + { + "type": "console", + "addr": "0x10007000", + "len": "0x1000", + "irq": 7, + "status": "enable" + } + ] } ] -} \ No newline at end of file +} diff --git a/platform/riscv64/qemu-plic/configs/zone1-linux-passthrough.json b/platform/riscv64/qemu-plic/configs/zone1-linux-passthrough.json index 11930624d..fd7a435c7 100644 --- a/platform/riscv64/qemu-plic/configs/zone1-linux-passthrough.json +++ b/platform/riscv64/qemu-plic/configs/zone1-linux-passthrough.json @@ -54,11 +54,14 @@ }], "num_pci_devs": 2, "alloc_pci_devs": [ - { + { "domain": "0x0", "bus": "0x0", "device": "0x0", "function": "0x0", + "v_bus": "0x0", + "v_device": "0x0", + "v_function": "0x0", "dev_type": "0" }, { @@ -66,6 +69,9 @@ "bus": "0x0", "device": "0x2", "function": "0x0", + "v_bus": "0x0", + "v_device": "0x2", + "v_function": "0x0", "dev_type": "0" } ] diff --git a/platform/riscv64/qemu-plic/configs/zone1-linux-virtio.json b/platform/riscv64/qemu-plic/configs/zone1-linux-virtio.json index 08bad2a15..564712984 100644 --- a/platform/riscv64/qemu-plic/configs/zone1-linux-virtio.json +++ b/platform/riscv64/qemu-plic/configs/zone1-linux-virtio.json @@ -53,6 +53,9 @@ "bus": "0x0", "device": "0x0", "function": "0x0", + "v_bus": "0x0", + "v_device": "0x0", + "v_function": "0x0", "dev_type": "0" }, { @@ -60,6 +63,9 @@ "bus": "0x0", "device": "0x2", "function": "0x0", + "v_bus": "0x0", + "v_device": "0x1", + "v_function": "0x0", "dev_type": "0" } ] diff --git a/platform/riscv64/qemu-plic/configs/zone1-linux.json b/platform/riscv64/qemu-plic/configs/zone1-linux.json new file mode 100644 index 000000000..a745e5338 --- /dev/null +++ b/platform/riscv64/qemu-plic/configs/zone1-linux.json @@ -0,0 +1,68 @@ +{ + "arch": "riscv", + "name": "linux2", + "zone_id": 1, + "cpus": [2, 3], + "memory_regions": [ + { + "type": "ram", + "physical_start": "0x85000000", + "virtual_start": "0x85000000", + "size": "0x0a000000" + }, + { + "type": "virtio", + "physical_start": "0x10006000", + "virtual_start": "0x10006000", + "size": "0x1000" + }, + { + "type": "virtio", + "physical_start": "0x10007000", + "virtual_start": "0x10007000", + "size": "0x1000" + } + ], + "interrupts": [6, 7], + "ivc_configs": [], + "kernel_filepath": "./Image", + "dtb_filepath": "./zone1-linux.dtb", + "kernel_load_paddr": "0x86000000", + "dtb_load_paddr": "0x85000000", + "entry_point": "0x86000000", + "arch_config": { + "plic_base": "0xc000000", + "plic_size": "0x4000000", + "aplic_base": "0xd000000", + "aplic_size": "0x8000" + }, + "pci_config": [{ + "ecam_base": "0x30000000", + "ecam_size": "0x10000000", + "io_base": "0x3000000", + "io_size": "0x10000", + "pci_io_base": "0x0", + "mem32_base": "0x40000000", + "mem32_size": "0x40000000", + "pci_mem32_base": "0x40000000", + "mem64_base": "0x400000000", + "mem64_size": "0x400000000", + "pci_mem64_base": "0x400000000", + "bus_range_begin": "0x0", + "bus_range_end": "0x1f", + "domain": "0x0" + }], + "num_pci_devs": 1, + "alloc_pci_devs": [ + { + "domain": "0x0", + "bus": "0x0", + "device": "0x0", + "function": "0x0", + "v_bus": "0x0", + "v_device": "0x0", + "v_function": "0x0", + "dev_type": "0" + } + ] +} diff --git a/platform/riscv64/qemu-plic/image/dts/zone1-linux.dts b/platform/riscv64/qemu-plic/image/dts/zone1-linux.dts index 9583df748..8087a6810 100644 --- a/platform/riscv64/qemu-plic/image/dts/zone1-linux.dts +++ b/platform/riscv64/qemu-plic/image/dts/zone1-linux.dts @@ -97,6 +97,13 @@ #address-cells = <0x03>; }; + virtio_mmio@10006000 { + interrupts = <0x06>; + interrupt-parent = <0x02>; + reg = <0x00 0x10006000 0x00 0x1000>; + compatible = "virtio,mmio"; + }; + virtio_mmio@10007000 { interrupts = <0x07>; interrupt-parent = <0x02>; diff --git a/platform/riscv64/qemu-plic/scripts/boot_zone1.sh b/platform/riscv64/qemu-plic/scripts/boot_zone1.sh index 00ec6b955..d3ffe3505 100644 --- a/platform/riscv64/qemu-plic/scripts/boot_zone1.sh +++ b/platform/riscv64/qemu-plic/scripts/boot_zone1.sh @@ -6,4 +6,4 @@ mount -t sysfs sysfs /sys mkdir -p /dev/pts mount -t devpts devpts /dev/pts ./hvisor virtio start virtio-backend.json & -./hvisor zone start zone1-linux-virtio.json \ No newline at end of file +./hvisor zone start zone1-linux.json diff --git a/src/pci/pci_access.rs b/src/pci/pci_access.rs index f2c6c77ff..d7b115b3e 100644 --- a/src/pci/pci_access.rs +++ b/src/pci/pci_access.rs @@ -1072,6 +1072,7 @@ pub enum BridgeField { SecondaryBusNumber, SubordinateBusNumber, SecondaryLatencyTimer, + BusNumbers, IOBase, IOLimit, SecondaryStatus, @@ -1108,6 +1109,7 @@ impl Debug for BridgeField { BridgeField::SecondaryBusNumber => write!(f, "SecondaryBusNumber"), BridgeField::SubordinateBusNumber => write!(f, "SubordinateBusNumber"), BridgeField::SecondaryLatencyTimer => write!(f, "SecondaryLatencyTimer"), + BridgeField::BusNumbers => write!(f, "BusNumbers"), BridgeField::IOBase => write!(f, "IOBase"), BridgeField::IOLimit => write!(f, "IOLimit"), BridgeField::SecondaryStatus => write!(f, "SecondaryStatus"), @@ -1146,6 +1148,7 @@ impl PciField for BridgeField { BridgeField::SecondaryBusNumber => 0x19, BridgeField::SubordinateBusNumber => 0x1a, BridgeField::SecondaryLatencyTimer => 0x1b, + BridgeField::BusNumbers => 0x18, BridgeField::IOBase => 0x1c, BridgeField::IOLimit => 0x1d, BridgeField::SecondaryStatus => 0x1e, @@ -1181,6 +1184,7 @@ impl PciField for BridgeField { BridgeField::SecondaryBusNumber => 1, BridgeField::SubordinateBusNumber => 1, BridgeField::SecondaryLatencyTimer => 1, + BridgeField::BusNumbers => 4, BridgeField::IOBase => 1, BridgeField::IOLimit => 1, BridgeField::SecondaryStatus => 2, @@ -1219,6 +1223,7 @@ impl BridgeField { (0x19, 1) => BridgeField::SecondaryBusNumber, (0x1a, 1) => BridgeField::SubordinateBusNumber, (0x1b, 1) => BridgeField::SecondaryLatencyTimer, + (0x18, 4) => BridgeField::BusNumbers, (0x1c, 1) => BridgeField::IOBase, (0x1d, 1) => BridgeField::IOLimit, (0x1e, 2) => BridgeField::SecondaryStatus, diff --git a/src/pci/pci_config.rs b/src/pci/pci_config.rs index 9612ccf00..0e8029342 100644 --- a/src/pci/pci_config.rs +++ b/src/pci/pci_config.rs @@ -331,7 +331,7 @@ impl Zone { }) { let mut vdev = dev.read().config_space.clone(); - vdev.set_vbdf(vbdf); + vdev.set_vbdf(vbdf, target_pci_config.bus_range_end as u8); let msi_count = vdev.get_msi_count(); domain_msi_count += msi_count; inner.vpci_bus_mut().insert(vbdf, vdev); @@ -351,7 +351,7 @@ impl Zone { } else { dev.set_zone_id(Some(_zone_id as u32)); let mut vdev_inner = dev.read().config_space.clone(); - vdev_inner.set_vbdf(vbdf); + vdev_inner.set_vbdf(vbdf, target_pci_config.bus_range_end as u8); let msi_count = vdev_inner.get_msi_count(); domain_msi_count += msi_count; inner.vpci_bus_mut().insert(vbdf, vdev_inner); diff --git a/src/pci/pci_handler.rs b/src/pci/pci_handler.rs index b1393f80e..ee0d42356 100644 --- a/src/pci/pci_handler.rs +++ b/src/pci/pci_handler.rs @@ -34,7 +34,7 @@ use crate::zone::is_this_root_zone; use super::pci_access::{BridgeField, EndpointField, HeaderType, PciField, PciMemType}; use super::pci_config::GLOBAL_PCIE_LIST; -use super::pci_struct::{ArcRwLockVirtualPciConfigSpace, BIT_LENTH}; +use super::pci_struct::{ArcRwLockVirtualPciConfigSpace, Bdf, BIT_LENTH}; use super::vpci_dev::VpciDevType; use super::PciConfigAddress; @@ -1606,6 +1606,38 @@ fn handle_pci_bridge_access( Ok(None) } } + BridgeField::PrimaryBusNumber + | BridgeField::SecondaryBusNumber + | BridgeField::SubordinateBusNumber + | BridgeField::SecondaryLatencyTimer => { + let reg_offset = field.to_offset() as usize; + if is_write { + if is_dev_belong_to_zone || (is_direct && is_root) { + dev.with_config_value_mut(|cv| { + let mut reg = cv.get_bridge_bus_reg(); + let shift = (reg_offset - 0x18) * 8; + reg = (reg & !((0xffu32) << shift)) | (((value as u32) & 0xff) << shift); + cv.set_bridge_bus_reg(reg); + }); + } + Ok(None) + } else { + let reg = dev.with_config_value(|cv| cv.get_bridge_bus_reg()); + Ok(Some(((reg >> ((reg_offset - 0x18) * 8)) & 0xff) as usize)) + } + } + BridgeField::BusNumbers => { + if is_write { + if is_dev_belong_to_zone || (is_direct && is_root) { + dev.with_config_value_mut(|cv| cv.set_bridge_bus_reg(value as u32)); + } + Ok(None) + } else { + Ok(Some( + dev.with_config_value(|cv| cv.get_bridge_bus_reg()) as usize + )) + } + } _ => Ok(None), } } @@ -1879,6 +1911,7 @@ pub fn mmio_dwc_cfg_handler(mmio: &mut MMIOAccess, _base: usize) -> HvResult { if let Some((atu, ecam_base)) = atu_config { // Get dbi_base from platform config (usually dbi_base == ecam_base) use crate::platform; + let pci_target = atu.pci_target(); if let Some(extend_config) = platform::ROOT_DWC_ATU_CONFIG .iter() .find(|cfg| cfg.ecam_base == ecam_base as u64) @@ -1889,7 +1922,6 @@ pub fn mmio_dwc_cfg_handler(mmio: &mut MMIOAccess, _base: usize) -> HvResult { let dbi_region = PciRegionMmio::new(dbi_base, dbi_size); let dbi_backend = DwcConfigRegionBackend::new(dbi_region); - let pci_target = atu.pci_target(); let target_bus = ((pci_target >> 24) & 0xff) as u8; let target_device = ((pci_target >> 19) & 0x1f) as u8; let target_function = ((pci_target >> 16) & 0x7) as u8; @@ -1903,7 +1935,7 @@ pub fn mmio_dwc_cfg_handler(mmio: &mut MMIOAccess, _base: usize) -> HvResult { && vbdf.device() == target_device && vbdf.function() == target_function { - Some((dev.get_bdf(), dev.get_parent_bus())) + Some(dev.get_bdf()) } else { None } @@ -1914,40 +1946,39 @@ pub fn mmio_dwc_cfg_handler(mmio: &mut MMIOAccess, _base: usize) -> HvResult { let mut atu_type = atu.atu_type(); let mut config_base = atu.cpu_base(); let mut cpu_limit = atu.cpu_limit(); - if let Some((host_bdf, parent_bus)) = mapped_target { + if let Some(host_bdf) = mapped_target { hw_pci_target = ((host_bdf.bus() as u64) << 24) + ((host_bdf.device() as u64) << 19) + ((host_bdf.function() as u64) << 16); - (config_base, atu_type) = if parent_bus == 0 { - (extend_config.cfg_base, AtuType::Cfg0) + let half = extend_config.cfg_size / 2; + config_base = _base as u64; + atu_type = if config_base >= extend_config.cfg_base + half { + AtuType::Cfg1 } else { - ( - extend_config.cfg_base + (extend_config.cfg_size / 2), - AtuType::Cfg1, - ) + AtuType::Cfg0 }; - cpu_limit = config_base + (extend_config.cfg_size / 2) - 1; + cpu_limit = config_base + half - 1; } - // Program hardware ATU with translated host target when remap exists. - let mut hw_atu = atu; - hw_atu.set_pci_target(hw_pci_target); - hw_atu.set_atu_type(atu_type); - hw_atu.set_cpu_base(config_base); - hw_atu.set_cpu_limit(cpu_limit); - AtuUnroll::dw_pcie_prog_outbound_atu_unroll(&dbi_backend, &hw_atu)?; + // Program hardware ATU only when host/guest BDF remapping is required. + if hw_pci_target != pci_target { + let mut hw_atu = atu; + hw_atu.set_pci_target(hw_pci_target); + hw_atu.set_atu_type(atu_type); + hw_atu.set_cpu_base(config_base); + hw_atu.set_cpu_limit(cpu_limit); + AtuUnroll::dw_pcie_prog_outbound_atu_unroll(&dbi_backend, &hw_atu)?; + } } let offset = (mmio.address & 0xfff) as PciConfigAddress; let zone = this_zone(); let mut is_dev_belong_to_zone = false; - let base = mmio.address as PciConfigAddress - offset + atu.pci_target(); - let dev: Option = { let mut guard = zone.write(); let vbus = guard.vpci_bus_mut(); - if let Some(dev) = vbus.get_device_by_base(base) { + if let Some(dev) = vbus.get_device_by_base(pci_target) { is_dev_belong_to_zone = true; Some(dev) } else { @@ -1956,13 +1987,26 @@ pub fn mmio_dwc_cfg_handler(mmio: &mut MMIOAccess, _base: usize) -> HvResult { // This avoids holding multiple locks simultaneously let dev_clone = { let global_pcie_list = GLOBAL_PCIE_LIST.lock(); - global_pcie_list - .values() - .find(|dev| { - let dev_guard = dev.read(); - dev_guard.get_base() == base - }) - .cloned() + let domain = platform::ROOT_PCI_CONFIG + .iter() + .find(|cfg| cfg.ecam_base as usize == ecam_base) + .map(|cfg| cfg.domain) + .unwrap_or(0); + let target_bdf = Bdf::new( + domain, + ((pci_target >> 24) & 0xff) as u8, + ((pci_target >> 19) & 0x1f) as u8, + ((pci_target >> 16) & 0x7) as u8, + ); + global_pcie_list.get(&target_bdf).cloned().or_else(|| { + global_pcie_list + .values() + .find(|dev| { + let dev_guard = dev.read(); + dev_guard.get_base() == pci_target + }) + .cloned() + }) }; dev_clone } @@ -2058,17 +2102,28 @@ pub fn mmio_vpci_handler_dbi(mmio: &mut MMIOAccess, _base: usize) -> HvResult { let root_config = platform::platform_root_zone_config(); let num_pci_bus = root_config.num_pci_bus as usize; - crate::pci::pci_config::hvisor_pci_init(&root_config.pci_config[..num_pci_bus])?; - let zone = crate::zone::root_zone(); let mut inner = zone.write(); - inner.virtual_pci_mmio_init_delay(&root_config.pci_config, num_pci_bus); + inner.virtual_pci_mmio_init_delay(&root_config.pci_config, num_pci_bus, domain_id); + drop(inner); + + if let Some(domain_cfg) = root_config.pci_config[..num_pci_bus] + .iter() + .find(|cfg| cfg.domain == domain_id && cfg.ecam_base != 0) + { + crate::pci::pci_config::hvisor_pci_init(core::slice::from_ref(domain_cfg))?; + } else { + warn!("No PCI config found for domain {}", domain_id); + } + + let mut inner = zone.write(); inner.guest_pci_init_delay( 0, &root_config.alloc_pci_devs, root_config.num_pci_devs, &root_config.pci_config, num_pci_bus, + domain_id, )?; #[cfg(dwc_msi)] diff --git a/src/pci/pci_struct.rs b/src/pci/pci_struct.rs index f272d8c28..d715099d8 100644 --- a/src/pci/pci_struct.rs +++ b/src/pci/pci_struct.rs @@ -55,6 +55,7 @@ pub struct ConfigValue { class_and_revision_id: (BaseClass, SubClass, Interface, DeviceRevision), bar_value: [u32; 6], rom_value: u32, + bridge_bus_reg: u32, } impl Default for ConfigValue { @@ -64,6 +65,7 @@ impl Default for ConfigValue { class_and_revision_id: (0xFFu8, 0u8, 0u8, 0u8), bar_value: [0; 6], rom_value: 0, + bridge_bus_reg: 0, } } } @@ -78,6 +80,7 @@ impl ConfigValue { class_and_revision_id, bar_value: [0; 6], rom_value: 0, + bridge_bus_reg: 0, } } @@ -143,6 +146,14 @@ impl ConfigValue { pub fn set_rom_value(&mut self, value: u32) { self.rom_value = value; } + + pub fn get_bridge_bus_reg(&self) -> u32 { + self.bridge_bus_reg + } + + pub fn set_bridge_bus_reg(&mut self, value: u32) { + self.bridge_bus_reg = value; + } } const MAX_DEVICE: u8 = 31; @@ -367,6 +378,7 @@ impl VirtualPciAccessBits { pub fn bridge() -> Self { let mut bits = BitArray::ZERO; bits[0x10..0x18].fill(true); // BARs + bits[0x18..0x1c].fill(true); // Primary/Secondary/Subordinate bus + latency bits[0x38..0x3c].fill(true); // ROM bits[0x34..0x38].fill(true); // Capability Pointer bits[0x40..0x100].fill(true); // Capability region (caps start at 0x40) @@ -1485,8 +1497,20 @@ impl VirtualPciConfigSpace { self.config_type } - pub fn set_vbdf(&mut self, vbdf: Bdf) { + pub fn set_vbdf(&mut self, vbdf: Bdf, domain_bus_range_end: u8) { self.vbdf = vbdf; + if self.config_type == HeaderType::PciBridge { + self.init_bridge_bus_reg(domain_bus_range_end); + } + } + + fn init_bridge_bus_reg(&mut self, domain_bus_range_end: u8) { + let primary = self.vbdf.bus(); + let secondary = primary.saturating_add(1); + let subordinate = domain_bus_range_end; + self.config_value.set_bridge_bus_reg( + ((subordinate as u32) << 16) | ((secondary as u32) << 8) | (primary as u32), + ); } pub fn get_base(&self) -> PciConfigAddress { @@ -1828,6 +1852,7 @@ impl PciIterator { } // Build MSI/MSIX info once during device discovery node.build_msi_info(); + node.set_vbdf(bdf, self.bus_range.end as u8); Some(node) } @@ -1859,6 +1884,7 @@ impl PciIterator { } // Build MSI/MSIX info once during device discovery node.build_msi_info(); + node.set_vbdf(bdf, self.bus_range.end as u8); Some(node) } diff --git a/src/pci/pci_test.rs b/src/pci/pci_test.rs index c6d7b22d4..629697181 100644 --- a/src/pci/pci_test.rs +++ b/src/pci/pci_test.rs @@ -78,7 +78,7 @@ pub fn pcie_guest_init() { // let _ = dev.write_hw(0x24, 4, 0xffffffff); // let value2 = dev.read_hw(0x24, 4).unwrap(); // info!("{:#?} bar64 {:x}, {:x}", bdf, (value1 as u64), ((value2 as u64) << 32u64)); - dev.set_vbdf(vbdf); + dev.set_vbdf(vbdf, 0xff); vbus.insert(vbdf, dev); } else { warn!("can not find dev"); @@ -87,7 +87,7 @@ pub fn pcie_guest_init() { let vbdf = Bdf::from_str("0000:00:02.0").unwrap(); let bdf = Bdf::from_str("0000:00:02.0").unwrap(); if let Some(mut dev) = guard.remove(&bdf) { - dev.set_vbdf(vbdf); + dev.set_vbdf(vbdf, 0xff); vbus.insert(vbdf, dev); } else { warn!("can not find dev"); @@ -96,7 +96,7 @@ pub fn pcie_guest_init() { let vbdf = Bdf::from_str("0000:00:03.0").unwrap(); let bdf = Bdf::from_str("0000:00:03.0").unwrap(); if let Some(mut dev) = guard.remove(&bdf) { - dev.set_vbdf(vbdf); + dev.set_vbdf(vbdf, 0xff); vbus.insert(vbdf, dev); } else { warn!("can not find dev"); diff --git a/src/zone.rs b/src/zone.rs index b9e68bc87..ed9137113 100644 --- a/src/zone.rs +++ b/src/zone.rs @@ -326,11 +326,12 @@ impl ZoneInner { num_pci_devs: u64, pci_config: &[HvPciConfig], _num_pci_config: usize, + domain_id: u8, ) -> HvResult { let guard = GLOBAL_PCIE_LIST.lock(); for target_pci_config in pci_config { // Skip empty config - if target_pci_config.ecam_base == 0 { + if target_pci_config.ecam_base == 0 || target_pci_config.domain != domain_id { continue; } @@ -455,7 +456,7 @@ impl ZoneInner { }) { let mut vdev = dev.read().config_space.clone(); - vdev.set_vbdf(vbdf); + vdev.set_vbdf(vbdf, target_pci_config.bus_range_end as u8); let msi_count = vdev.get_msi_count(); domain_msi_count += msi_count; self.vpci_bus_mut().insert(vbdf, vdev); @@ -475,7 +476,7 @@ impl ZoneInner { } else { dev.set_zone_id(Some(_zone_id as u32)); let mut vdev_inner = dev.read().config_space.clone(); - vdev_inner.set_vbdf(vbdf); + vdev_inner.set_vbdf(vbdf, target_pci_config.bus_range_end as u8); let msi_count = vdev_inner.get_msi_count(); domain_msi_count += msi_count; self.vpci_bus_mut().insert(vbdf, vdev_inner); @@ -569,7 +570,9 @@ impl ZoneInner { pci_rootcomplex_config: &[HvPciConfig; CONFIG_PCI_BUS_MAXNUM], _num_pci_config: usize, ) { + use crate::memory::mmio_generic_handler; use crate::pci::pci_handler::mmio_vpci_handler_dbi; + use crate::platform; for rootcomplex_config in pci_rootcomplex_config { if rootcomplex_config.ecam_base == 0 { @@ -584,6 +587,20 @@ impl ZoneInner { mmio_vpci_handler_dbi, encoded_arg, ); + + let extend_config = platform::ROOT_DWC_ATU_CONFIG + .iter() + .find(|cfg| cfg.ecam_base == rootcomplex_config.ecam_base); + if let Some(extend_config) = extend_config { + if extend_config.apb_base != 0 && extend_config.apb_size != 0 { + self.mmio_region_register( + extend_config.apb_base as usize, + extend_config.apb_size as usize, + mmio_generic_handler, + extend_config.apb_base as usize, + ); + } + } } } @@ -592,12 +609,13 @@ impl ZoneInner { &mut self, pci_rootcomplex_config: &[HvPciConfig; CONFIG_PCI_BUS_MAXNUM], _num_pci_config: usize, + domain_id: u8, ) { #[cfg(loongarch64_pcie)] let mut emergency_map_regions: alloc::vec::Vec<(usize, usize)> = alloc::vec::Vec::new(); for rootcomplex_config in pci_rootcomplex_config { - if rootcomplex_config.ecam_base == 0 { + if rootcomplex_config.ecam_base == 0 || rootcomplex_config.domain != domain_id { continue; } #[cfg(ecam_pcie)]