mirror of
https://github.com/xemu-project/xemu.git
synced 2026-07-11 01:24:41 +02:00
Merge QEMU v10.2.0
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Analyse lock events and compute statistics
|
||||
#
|
||||
|
||||
@@ -53,7 +53,7 @@ echo $(grep_include -F 'trace/generated-tracers.h') files include generated-trac
|
||||
echo $(grep_include -F 'qapi/error.h') files include qapi/error.h
|
||||
echo $(grep_include -F 'qom/object.h') files include qom/object.h
|
||||
echo $(grep_include -F 'block/aio.h') files include block/aio.h
|
||||
echo $(grep_include -F 'exec/memory.h') files include exec/memory.h
|
||||
echo $(grep_include -F 'system/memory.h') files include system/memory.h
|
||||
echo $(grep_include -F 'fpu/softfloat.h') files include fpu/softfloat.h
|
||||
echo $(grep_include -F 'qemu/bswap.h') files include qemu/bswap.h
|
||||
echo
|
||||
|
||||
@@ -620,7 +620,9 @@ class MigrationDump(object):
|
||||
QEMU_VM_SUBSECTION = 0x05
|
||||
QEMU_VM_VMDESCRIPTION = 0x06
|
||||
QEMU_VM_CONFIGURATION = 0x07
|
||||
QEMU_VM_COMMAND = 0x08
|
||||
QEMU_VM_SECTION_FOOTER= 0x7e
|
||||
QEMU_MIG_CMD_SWITCHOVER_START = 0x0b
|
||||
|
||||
def __init__(self, filename):
|
||||
self.section_classes = {
|
||||
@@ -685,6 +687,15 @@ class MigrationDump(object):
|
||||
elif section_type == self.QEMU_VM_SECTION_PART or section_type == self.QEMU_VM_SECTION_END:
|
||||
section_id = file.read32()
|
||||
self.sections[section_id].read()
|
||||
elif section_type == self.QEMU_VM_COMMAND:
|
||||
command_type = file.read16()
|
||||
command_data_len = file.read16()
|
||||
if command_type != self.QEMU_MIG_CMD_SWITCHOVER_START:
|
||||
raise Exception("Unknown QEMU_VM_COMMAND: %x" %
|
||||
(command_type))
|
||||
if command_data_len != 0:
|
||||
raise Exception("Invalid SWITCHOVER_START length: %x" %
|
||||
(command_data_len))
|
||||
elif section_type == self.QEMU_VM_SECTION_FOOTER:
|
||||
read_section_id = file.read32()
|
||||
if read_section_id != section_id:
|
||||
|
||||
+41
-12
@@ -29,17 +29,47 @@ sub_file="${sub_tdir}/submodule.tar"
|
||||
# independent of what the developer currently has initialized
|
||||
# in their checkout, because the build environment is completely
|
||||
# different to the host OS.
|
||||
subprojects="keycodemapdb libvfio-user berkeley-softfloat-3
|
||||
berkeley-testfloat-3 arbitrary-int-1-rs bilge-0.2-rs
|
||||
bilge-impl-0.2-rs either-1-rs itertools-0.11-rs proc-macro2-1-rs
|
||||
proc-macro-error-1-rs proc-macro-error-attr-1-rs quote-1-rs
|
||||
syn-2-rs unicode-ident-1-rs"
|
||||
subprojects=(
|
||||
anyhow-1-rs
|
||||
arbitrary-int-1-rs
|
||||
attrs-0.2-rs
|
||||
berkeley-softfloat-3
|
||||
berkeley-testfloat-3
|
||||
bilge-0.2-rs
|
||||
bilge-impl-0.2-rs
|
||||
either-1-rs
|
||||
foreign-0.3-rs
|
||||
glib-sys-0.21-rs
|
||||
itertools-0.11-rs
|
||||
keycodemapdb
|
||||
libc-0.2-rs
|
||||
libvfio-user
|
||||
proc-macro-error-1-rs
|
||||
proc-macro-error-attr-1-rs
|
||||
proc-macro2-1-rs
|
||||
quote-1-rs
|
||||
syn-2-rs
|
||||
unicode-ident-1-rs
|
||||
)
|
||||
sub_deinit=""
|
||||
|
||||
# xemu only
|
||||
subprojects="keycodemapdb berkeley-softfloat-3 berkeley-testfloat-3
|
||||
glslang SPIRV-Reflect volk VulkanMemoryAllocator nv2a_vsh_cpu
|
||||
tomlplusplus xxhash imgui implot genconfig json"
|
||||
subprojects=(
|
||||
berkeley-softfloat-3
|
||||
berkeley-testfloat-3
|
||||
genconfig
|
||||
glslang
|
||||
imgui
|
||||
implot
|
||||
json
|
||||
keycodemapdb
|
||||
nv2a_vsh_cpu
|
||||
SPIRV-Reflect
|
||||
tomlplusplus
|
||||
volk
|
||||
VulkanMemoryAllocator
|
||||
xxhash
|
||||
)
|
||||
|
||||
function cleanup() {
|
||||
local status=$?
|
||||
@@ -84,12 +114,11 @@ function subproject_dir() {
|
||||
git archive --format tar "$(tree_ish)" --prefix="$tar_prefix" > "$tar_file"
|
||||
test $? -ne 0 && error "failed to archive qemu"
|
||||
|
||||
meson subprojects download -j8 $subprojects
|
||||
# test $? -ne 0 && error "failed to download subprojects"
|
||||
meson subprojects download -j8 ${subprojects[@]} >/dev/null
|
||||
# test $? -ne 0 && error "failed to download subprojects $subprojects"
|
||||
|
||||
for sp in $subprojects; do
|
||||
for sp in "${subprojects[@]}"; do
|
||||
tar --append --file "$tar_file" --exclude=.git --transform "s,^./,$tar_prefix," ./subprojects/"$(subproject_dir $sp)"
|
||||
test $? -ne 0 && error "failed to append subproject $sp to $tar_file"
|
||||
done
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -0,0 +1,476 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# pylint: disable=C0301,C0114,R0903,R0912,R0913,R0914,R0915,W0511
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#
|
||||
# Copyright (C) 2024-2025 Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
|
||||
|
||||
# TODO: current implementation has dummy defaults.
|
||||
#
|
||||
# For a better implementation, a QMP addition/call is needed to
|
||||
# retrieve some data for ARM Processor Error injection:
|
||||
#
|
||||
# - ARM registers: power_state, mpidr.
|
||||
|
||||
"""
|
||||
Generate an ARM processor error CPER, compatible with
|
||||
UEFI 2.9A Errata.
|
||||
|
||||
Injecting such errors can be done using:
|
||||
|
||||
$ ./scripts/ghes_inject.py arm
|
||||
Error injected.
|
||||
|
||||
Produces a simple CPER register, as detected on a Linux guest:
|
||||
|
||||
[Hardware Error]: Hardware error from APEI Generic Hardware Error Source: 1
|
||||
[Hardware Error]: event severity: recoverable
|
||||
[Hardware Error]: Error 0, type: recoverable
|
||||
[Hardware Error]: section_type: ARM processor error
|
||||
[Hardware Error]: MIDR: 0x0000000000000000
|
||||
[Hardware Error]: running state: 0x0
|
||||
[Hardware Error]: Power State Coordination Interface state: 0
|
||||
[Hardware Error]: Error info structure 0:
|
||||
[Hardware Error]: num errors: 2
|
||||
[Hardware Error]: error_type: 0x02: cache error
|
||||
[Hardware Error]: error_info: 0x000000000091000f
|
||||
[Hardware Error]: transaction type: Data Access
|
||||
[Hardware Error]: cache error, operation type: Data write
|
||||
[Hardware Error]: cache level: 2
|
||||
[Hardware Error]: processor context not corrupted
|
||||
[Firmware Warn]: GHES: Unhandled processor error type 0x02: cache error
|
||||
|
||||
The ARM Processor Error message can be customized via command line
|
||||
parameters. For instance:
|
||||
|
||||
$ ./scripts/ghes_inject.py arm --mpidr 0x444 --running --affinity 1 \
|
||||
--error-info 12345678 --vendor 0x13,123,4,5,1 --ctx-array 0,1,2,3,4,5 \
|
||||
-t cache tlb bus micro-arch tlb,micro-arch
|
||||
Error injected.
|
||||
|
||||
Injects this error, as detected on a Linux guest:
|
||||
|
||||
[Hardware Error]: Hardware error from APEI Generic Hardware Error Source: 1
|
||||
[Hardware Error]: event severity: recoverable
|
||||
[Hardware Error]: Error 0, type: recoverable
|
||||
[Hardware Error]: section_type: ARM processor error
|
||||
[Hardware Error]: MIDR: 0x0000000000000000
|
||||
[Hardware Error]: Multiprocessor Affinity Register (MPIDR): 0x0000000000000000
|
||||
[Hardware Error]: error affinity level: 0
|
||||
[Hardware Error]: running state: 0x1
|
||||
[Hardware Error]: Power State Coordination Interface state: 0
|
||||
[Hardware Error]: Error info structure 0:
|
||||
[Hardware Error]: num errors: 2
|
||||
[Hardware Error]: error_type: 0x02: cache error
|
||||
[Hardware Error]: error_info: 0x0000000000bc614e
|
||||
[Hardware Error]: cache level: 2
|
||||
[Hardware Error]: processor context not corrupted
|
||||
[Hardware Error]: Error info structure 1:
|
||||
[Hardware Error]: num errors: 2
|
||||
[Hardware Error]: error_type: 0x04: TLB error
|
||||
[Hardware Error]: error_info: 0x000000000054007f
|
||||
[Hardware Error]: transaction type: Instruction
|
||||
[Hardware Error]: TLB error, operation type: Instruction fetch
|
||||
[Hardware Error]: TLB level: 1
|
||||
[Hardware Error]: processor context not corrupted
|
||||
[Hardware Error]: the error has not been corrected
|
||||
[Hardware Error]: PC is imprecise
|
||||
[Hardware Error]: Error info structure 2:
|
||||
[Hardware Error]: num errors: 2
|
||||
[Hardware Error]: error_type: 0x08: bus error
|
||||
[Hardware Error]: error_info: 0x00000080d6460fff
|
||||
[Hardware Error]: transaction type: Generic
|
||||
[Hardware Error]: bus error, operation type: Generic read (type of instruction or data request cannot be determined)
|
||||
[Hardware Error]: affinity level at which the bus error occurred: 1
|
||||
[Hardware Error]: processor context corrupted
|
||||
[Hardware Error]: the error has been corrected
|
||||
[Hardware Error]: PC is imprecise
|
||||
[Hardware Error]: Program execution can be restarted reliably at the PC associated with the error.
|
||||
[Hardware Error]: participation type: Local processor observed
|
||||
[Hardware Error]: request timed out
|
||||
[Hardware Error]: address space: External Memory Access
|
||||
[Hardware Error]: memory access attributes:0x20
|
||||
[Hardware Error]: access mode: secure
|
||||
[Hardware Error]: Error info structure 3:
|
||||
[Hardware Error]: num errors: 2
|
||||
[Hardware Error]: error_type: 0x10: micro-architectural error
|
||||
[Hardware Error]: error_info: 0x0000000078da03ff
|
||||
[Hardware Error]: Error info structure 4:
|
||||
[Hardware Error]: num errors: 2
|
||||
[Hardware Error]: error_type: 0x14: TLB error|micro-architectural error
|
||||
[Hardware Error]: Context info structure 0:
|
||||
[Hardware Error]: register context type: AArch64 EL1 context registers
|
||||
[Hardware Error]: 00000000: 00000000 00000000
|
||||
[Hardware Error]: Vendor specific error info has 5 bytes:
|
||||
[Hardware Error]: 00000000: 13 7b 04 05 01 .{...
|
||||
[Firmware Warn]: GHES: Unhandled processor error type 0x02: cache error
|
||||
[Firmware Warn]: GHES: Unhandled processor error type 0x04: TLB error
|
||||
[Firmware Warn]: GHES: Unhandled processor error type 0x08: bus error
|
||||
[Firmware Warn]: GHES: Unhandled processor error type 0x10: micro-architectural error
|
||||
[Firmware Warn]: GHES: Unhandled processor error type 0x14: TLB error|micro-architectural error
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import re
|
||||
|
||||
from qmp_helper import qmp, util, cper_guid
|
||||
|
||||
|
||||
class ArmProcessorEinj:
|
||||
"""
|
||||
Implements ARM Processor Error injection via GHES
|
||||
"""
|
||||
|
||||
DESC = """
|
||||
Generates an ARM processor error CPER, compatible with
|
||||
UEFI 2.9A Errata.
|
||||
"""
|
||||
|
||||
ACPI_GHES_ARM_CPER_LENGTH = 40
|
||||
ACPI_GHES_ARM_CPER_PEI_LENGTH = 32
|
||||
|
||||
# Context types
|
||||
CONTEXT_AARCH32_EL1 = 1
|
||||
CONTEXT_AARCH64_EL1 = 5
|
||||
CONTEXT_MISC_REG = 8
|
||||
|
||||
def __init__(self, subparsers):
|
||||
"""Initialize the error injection class and add subparser"""
|
||||
|
||||
# Valid choice values
|
||||
self.arm_valid_bits = {
|
||||
"mpidr": util.bit(0),
|
||||
"affinity": util.bit(1),
|
||||
"running": util.bit(2),
|
||||
"vendor": util.bit(3),
|
||||
}
|
||||
|
||||
self.pei_flags = {
|
||||
"first": util.bit(0),
|
||||
"last": util.bit(1),
|
||||
"propagated": util.bit(2),
|
||||
"overflow": util.bit(3),
|
||||
}
|
||||
|
||||
self.pei_error_types = {
|
||||
"cache": util.bit(1),
|
||||
"tlb": util.bit(2),
|
||||
"bus": util.bit(3),
|
||||
"micro-arch": util.bit(4),
|
||||
}
|
||||
|
||||
self.pei_valid_bits = {
|
||||
"multiple-error": util.bit(0),
|
||||
"flags": util.bit(1),
|
||||
"error-info": util.bit(2),
|
||||
"virt-addr": util.bit(3),
|
||||
"phy-addr": util.bit(4),
|
||||
}
|
||||
|
||||
self.data = bytearray()
|
||||
|
||||
parser = subparsers.add_parser("arm", description=self.DESC)
|
||||
|
||||
arm_valid_bits = ",".join(self.arm_valid_bits.keys())
|
||||
flags = ",".join(self.pei_flags.keys())
|
||||
error_types = ",".join(self.pei_error_types.keys())
|
||||
pei_valid_bits = ",".join(self.pei_valid_bits.keys())
|
||||
|
||||
# UEFI N.16 ARM Validation bits
|
||||
g_arm = parser.add_argument_group("ARM processor")
|
||||
g_arm.add_argument("--arm", "--arm-valid",
|
||||
help=f"ARM valid bits: {arm_valid_bits}")
|
||||
g_arm.add_argument("-a", "--affinity", "--level", "--affinity-level",
|
||||
type=lambda x: int(x, 0),
|
||||
help="Affinity level (when multiple levels apply)")
|
||||
g_arm.add_argument("-l", "--mpidr", type=lambda x: int(x, 0),
|
||||
help="Multiprocessor Affinity Register")
|
||||
g_arm.add_argument("-i", "--midr", type=lambda x: int(x, 0),
|
||||
help="Main ID Register")
|
||||
g_arm.add_argument("-r", "--running",
|
||||
action=argparse.BooleanOptionalAction,
|
||||
default=None,
|
||||
help="Indicates if the processor is running or not")
|
||||
g_arm.add_argument("--psci", "--psci-state",
|
||||
type=lambda x: int(x, 0),
|
||||
help="Power State Coordination Interface - PSCI state")
|
||||
|
||||
# TODO: Add vendor-specific support
|
||||
|
||||
# UEFI N.17 bitmaps (type and flags)
|
||||
g_pei = parser.add_argument_group("ARM Processor Error Info (PEI)")
|
||||
g_pei.add_argument("-t", "--type", nargs="+",
|
||||
help=f"one or more error types: {error_types}")
|
||||
g_pei.add_argument("-f", "--flags", nargs="*",
|
||||
help=f"zero or more error flags: {flags}")
|
||||
g_pei.add_argument("-V", "--pei-valid", "--error-valid", nargs="*",
|
||||
help=f"zero or more PEI valid bits: {pei_valid_bits}")
|
||||
|
||||
# UEFI N.17 Integer values
|
||||
g_pei.add_argument("-m", "--multiple-error", nargs="+",
|
||||
help="Number of errors: 0: Single error, 1: Multiple errors, 2-65535: Error count if known")
|
||||
g_pei.add_argument("-e", "--error-info", nargs="+",
|
||||
help="Error information (UEFI 2.10 tables N.18 to N.20)")
|
||||
g_pei.add_argument("-p", "--physical-address", nargs="+",
|
||||
help="Physical address")
|
||||
g_pei.add_argument("-v", "--virtual-address", nargs="+",
|
||||
help="Virtual address")
|
||||
|
||||
# UEFI N.21 Context
|
||||
g_ctx = parser.add_argument_group("Processor Context")
|
||||
g_ctx.add_argument("--ctx-type", "--context-type", nargs="*",
|
||||
help="Type of the context (0=ARM32 GPR, 5=ARM64 EL1, other values supported)")
|
||||
g_ctx.add_argument("--ctx-size", "--context-size", nargs="*",
|
||||
help="Minimal size of the context")
|
||||
g_ctx.add_argument("--ctx-array", "--context-array", nargs="*",
|
||||
help="Comma-separated arrays for each context")
|
||||
|
||||
# Vendor-specific data
|
||||
g_vendor = parser.add_argument_group("Vendor-specific data")
|
||||
g_vendor.add_argument("--vendor", "--vendor-specific", nargs="+",
|
||||
help="Vendor-specific byte arrays of data")
|
||||
|
||||
# Add arguments for Generic Error Data
|
||||
qmp.argparse(parser)
|
||||
|
||||
parser.set_defaults(func=self.send_cper)
|
||||
|
||||
def send_cper(self, args):
|
||||
"""Parse subcommand arguments and send a CPER via QMP"""
|
||||
|
||||
qmp_cmd = qmp(args.host, args.port, args.debug)
|
||||
|
||||
# Handle Generic Error Data arguments if any
|
||||
qmp_cmd.set_args(args)
|
||||
|
||||
is_cpu_type = re.compile(r"^([\w+]+\-)?arm\-cpu$")
|
||||
cpus = qmp_cmd.search_qom("/machine/unattached/device",
|
||||
"type", is_cpu_type)
|
||||
|
||||
cper = {}
|
||||
pei = {}
|
||||
ctx = {}
|
||||
vendor = {}
|
||||
|
||||
arg = vars(args)
|
||||
|
||||
# Handle global parameters
|
||||
if args.arm:
|
||||
arm_valid_init = False
|
||||
cper["valid"] = util.get_choice(name="valid",
|
||||
value=args.arm,
|
||||
choices=self.arm_valid_bits,
|
||||
suffixes=["-error", "-err"])
|
||||
else:
|
||||
cper["valid"] = 0
|
||||
arm_valid_init = True
|
||||
|
||||
if "running" in arg:
|
||||
if args.running:
|
||||
cper["running-state"] = util.bit(0)
|
||||
else:
|
||||
cper["running-state"] = 0
|
||||
else:
|
||||
cper["running-state"] = 0
|
||||
|
||||
if arm_valid_init:
|
||||
if args.affinity:
|
||||
cper["valid"] |= self.arm_valid_bits["affinity"]
|
||||
|
||||
if args.mpidr:
|
||||
cper["valid"] |= self.arm_valid_bits["mpidr"]
|
||||
|
||||
if "running-state" in cper:
|
||||
cper["valid"] |= self.arm_valid_bits["running"]
|
||||
|
||||
if args.psci:
|
||||
cper["valid"] |= self.arm_valid_bits["running"]
|
||||
|
||||
# Handle PEI
|
||||
if not args.type:
|
||||
args.type = ["cache-error"]
|
||||
|
||||
util.get_mult_choices(
|
||||
pei,
|
||||
name="valid",
|
||||
values=args.pei_valid,
|
||||
choices=self.pei_valid_bits,
|
||||
suffixes=["-valid", "--addr"],
|
||||
)
|
||||
util.get_mult_choices(
|
||||
pei,
|
||||
name="type",
|
||||
values=args.type,
|
||||
choices=self.pei_error_types,
|
||||
suffixes=["-error", "-err"],
|
||||
)
|
||||
util.get_mult_choices(
|
||||
pei,
|
||||
name="flags",
|
||||
values=args.flags,
|
||||
choices=self.pei_flags,
|
||||
suffixes=["-error", "-cap"],
|
||||
)
|
||||
util.get_mult_int(pei, "error-info", args.error_info)
|
||||
util.get_mult_int(pei, "multiple-error", args.multiple_error)
|
||||
util.get_mult_int(pei, "phy-addr", args.physical_address)
|
||||
util.get_mult_int(pei, "virt-addr", args.virtual_address)
|
||||
|
||||
# Handle context
|
||||
util.get_mult_int(ctx, "type", args.ctx_type, allow_zero=True)
|
||||
util.get_mult_int(ctx, "minimal-size", args.ctx_size, allow_zero=True)
|
||||
util.get_mult_array(ctx, "register", args.ctx_array, allow_zero=True)
|
||||
|
||||
util.get_mult_array(vendor, "bytes", args.vendor, max_val=255)
|
||||
|
||||
# Store PEI
|
||||
pei_data = bytearray()
|
||||
default_flags = self.pei_flags["first"]
|
||||
default_flags |= self.pei_flags["last"]
|
||||
|
||||
error_info_num = 0
|
||||
|
||||
for i, p in pei.items(): # pylint: disable=W0612
|
||||
error_info_num += 1
|
||||
|
||||
# UEFI 2.10 doesn't define how to encode error information
|
||||
# when multiple types are raised. So, provide a default only
|
||||
# if a single type is there
|
||||
if "error-info" not in p:
|
||||
if p["type"] == util.bit(1):
|
||||
p["error-info"] = 0x0091000F
|
||||
if p["type"] == util.bit(2):
|
||||
p["error-info"] = 0x0054007F
|
||||
if p["type"] == util.bit(3):
|
||||
p["error-info"] = 0x80D6460FFF
|
||||
if p["type"] == util.bit(4):
|
||||
p["error-info"] = 0x78DA03FF
|
||||
|
||||
if "valid" not in p:
|
||||
p["valid"] = 0
|
||||
if "multiple-error" in p:
|
||||
p["valid"] |= self.pei_valid_bits["multiple-error"]
|
||||
|
||||
if "flags" in p:
|
||||
p["valid"] |= self.pei_valid_bits["flags"]
|
||||
|
||||
if "error-info" in p:
|
||||
p["valid"] |= self.pei_valid_bits["error-info"]
|
||||
|
||||
if "phy-addr" in p:
|
||||
p["valid"] |= self.pei_valid_bits["phy-addr"]
|
||||
|
||||
if "virt-addr" in p:
|
||||
p["valid"] |= self.pei_valid_bits["virt-addr"]
|
||||
|
||||
# Version
|
||||
util.data_add(pei_data, 0, 1)
|
||||
|
||||
util.data_add(pei_data,
|
||||
self.ACPI_GHES_ARM_CPER_PEI_LENGTH, 1)
|
||||
|
||||
util.data_add(pei_data, p["valid"], 2)
|
||||
util.data_add(pei_data, p["type"], 1)
|
||||
util.data_add(pei_data, p.get("multiple-error", 1), 2)
|
||||
util.data_add(pei_data, p.get("flags", default_flags), 1)
|
||||
util.data_add(pei_data, p.get("error-info", 0), 8)
|
||||
util.data_add(pei_data, p.get("virt-addr", 0xDEADBEEF), 8)
|
||||
util.data_add(pei_data, p.get("phy-addr", 0xABBA0BAD), 8)
|
||||
|
||||
# Store Context
|
||||
ctx_data = bytearray()
|
||||
context_info_num = 0
|
||||
|
||||
if ctx:
|
||||
ret = qmp_cmd.send_cmd("query-target", may_open=True)
|
||||
|
||||
default_ctx = self.CONTEXT_MISC_REG
|
||||
|
||||
if "arch" in ret:
|
||||
if ret["arch"] == "aarch64":
|
||||
default_ctx = self.CONTEXT_AARCH64_EL1
|
||||
elif ret["arch"] == "arm":
|
||||
default_ctx = self.CONTEXT_AARCH32_EL1
|
||||
|
||||
for k in sorted(ctx.keys()):
|
||||
context_info_num += 1
|
||||
|
||||
if "type" not in ctx[k]:
|
||||
ctx[k]["type"] = default_ctx
|
||||
|
||||
if "register" not in ctx[k]:
|
||||
ctx[k]["register"] = []
|
||||
|
||||
reg_size = len(ctx[k]["register"])
|
||||
size = 0
|
||||
|
||||
if "minimal-size" in ctx:
|
||||
size = ctx[k]["minimal-size"]
|
||||
|
||||
size = max(size, reg_size)
|
||||
|
||||
size = (size + 1) % 0xFFFE
|
||||
|
||||
# Version
|
||||
util.data_add(ctx_data, 0, 2)
|
||||
|
||||
util.data_add(ctx_data, ctx[k]["type"], 2)
|
||||
|
||||
util.data_add(ctx_data, 8 * size, 4)
|
||||
|
||||
for r in ctx[k]["register"]:
|
||||
util.data_add(ctx_data, r, 8)
|
||||
|
||||
for i in range(reg_size, size): # pylint: disable=W0612
|
||||
util.data_add(ctx_data, 0, 8)
|
||||
|
||||
# Vendor-specific bytes are not grouped
|
||||
vendor_data = bytearray()
|
||||
if vendor:
|
||||
for k in sorted(vendor.keys()):
|
||||
for b in vendor[k]["bytes"]:
|
||||
util.data_add(vendor_data, b, 1)
|
||||
|
||||
# Encode ARM Processor Error
|
||||
data = bytearray()
|
||||
|
||||
util.data_add(data, cper["valid"], 4)
|
||||
|
||||
util.data_add(data, error_info_num, 2)
|
||||
util.data_add(data, context_info_num, 2)
|
||||
|
||||
# Calculate the length of the CPER data
|
||||
cper_length = self.ACPI_GHES_ARM_CPER_LENGTH
|
||||
cper_length += len(pei_data)
|
||||
cper_length += len(vendor_data)
|
||||
cper_length += len(ctx_data)
|
||||
util.data_add(data, cper_length, 4)
|
||||
|
||||
util.data_add(data, arg.get("affinity-level", 0), 1)
|
||||
|
||||
# Reserved
|
||||
util.data_add(data, 0, 3)
|
||||
|
||||
if "midr-el1" not in arg:
|
||||
if cpus:
|
||||
cmd_arg = {
|
||||
'path': cpus[0],
|
||||
'property': "midr"
|
||||
}
|
||||
ret = qmp_cmd.send_cmd("qom-get", cmd_arg, may_open=True)
|
||||
if isinstance(ret, int):
|
||||
arg["midr-el1"] = ret
|
||||
|
||||
util.data_add(data, arg.get("mpidr-el1", 0), 8)
|
||||
util.data_add(data, arg.get("midr-el1", 0), 8)
|
||||
util.data_add(data, cper["running-state"], 4)
|
||||
util.data_add(data, arg.get("psci-state", 0), 4)
|
||||
|
||||
# Add PEI
|
||||
data.extend(pei_data)
|
||||
data.extend(ctx_data)
|
||||
data.extend(vendor_data)
|
||||
|
||||
self.data = data
|
||||
|
||||
qmp_cmd.send_cper(cper_guid.CPER_PROC_ARM, self.data)
|
||||
+326
-97
@@ -365,6 +365,18 @@ our @typeList = (
|
||||
qr{guintptr},
|
||||
);
|
||||
|
||||
# Match text found in common license boilerplate comments:
|
||||
# for new files the SPDX-License-Identifier line is sufficient.
|
||||
our @LICENSE_BOILERPLATE = (
|
||||
"licensed under the terms of the GNU GPL",
|
||||
"under the terms of the GNU General Public License",
|
||||
"under the terms of the GNU Lesser General Public",
|
||||
"Permission is hereby granted, free of charge",
|
||||
"GNU GPL, version 2 or later",
|
||||
"See the COPYING file"
|
||||
);
|
||||
our $LICENSE_BOILERPLATE_RE = join("|", @LICENSE_BOILERPLATE);
|
||||
|
||||
# Load common spelling mistakes and build regular expression list.
|
||||
my $misspellings;
|
||||
my %spelling_fix;
|
||||
@@ -1330,26 +1342,182 @@ sub WARN {
|
||||
}
|
||||
}
|
||||
|
||||
# According to tests/qtest/bios-tables-test.c: do not
|
||||
# change expected file in the same commit with adding test
|
||||
sub checkfilename {
|
||||
my ($name, $acpi_testexpected, $acpi_nontestexpected) = @_;
|
||||
sub checkspdx {
|
||||
my ($file, $expr) = @_;
|
||||
|
||||
# Note: shell script that rebuilds the expected files is in the same
|
||||
# directory as files themselves.
|
||||
# Note: allowed diff list can be changed both when changing expected
|
||||
# files and when changing tests.
|
||||
if ($name =~ m#^tests/data/acpi/# and not $name =~ m#^\.sh$#) {
|
||||
$$acpi_testexpected = $name;
|
||||
} elsif ($name !~ m#^tests/qtest/bios-tables-test-allowed-diff.h$#) {
|
||||
$$acpi_nontestexpected = $name;
|
||||
# Imported Linux headers probably have SPDX tags, but if they
|
||||
# don't we're not requiring contributors to fix this, as these
|
||||
# files are not expected to be modified locally in QEMU.
|
||||
# Also don't accidentally detect own checking code.
|
||||
if ($file =~ m,include/standard-headers, ||
|
||||
$file =~ m,linux-headers, ||
|
||||
$file =~ m,checkpatch.pl,) {
|
||||
return;
|
||||
}
|
||||
|
||||
my $origexpr = $expr;
|
||||
|
||||
# Flatten sub-expressions
|
||||
$expr =~ s/\(|\)/ /g;
|
||||
$expr =~ s/OR|AND/ /g;
|
||||
|
||||
# Merge WITH exceptions to the license
|
||||
$expr =~ s/\s+WITH\s+/-WITH-/g;
|
||||
|
||||
# Cull more leading/trailing whitespace
|
||||
$expr =~ s/^\s*//g;
|
||||
$expr =~ s/\s*$//g;
|
||||
|
||||
# Cull C comment end
|
||||
$expr =~ s/\*\/.*//;
|
||||
|
||||
my @bits = split / +/, $expr;
|
||||
|
||||
my $prefer = "GPL-2.0-or-later";
|
||||
my @valid = qw(
|
||||
GPL-2.0-only
|
||||
LGPL-2.1-only
|
||||
LGPL-2.1-or-later
|
||||
BSD-2-Clause
|
||||
BSD-3-Clause
|
||||
MIT
|
||||
);
|
||||
|
||||
my $nonpreferred = 0;
|
||||
my @unknown = ();
|
||||
foreach my $bit (@bits) {
|
||||
if ($bit eq $prefer) {
|
||||
next;
|
||||
}
|
||||
if (defined $$acpi_testexpected and defined $$acpi_nontestexpected) {
|
||||
if (grep /^$bit$/, @valid) {
|
||||
$nonpreferred = 1;
|
||||
} else {
|
||||
push @unknown, $bit;
|
||||
}
|
||||
}
|
||||
if (@unknown) {
|
||||
ERROR("Saw unacceptable licenses '" . join(',', @unknown) .
|
||||
"', valid choices for QEMU are:\n" . join("\n", $prefer, @valid));
|
||||
}
|
||||
|
||||
if ($nonpreferred) {
|
||||
WARN("Saw acceptable license '$origexpr' but note '$prefer' is " .
|
||||
"preferred for new files unless the code is derived from a " .
|
||||
"source file with an existing declared license that must be " .
|
||||
"retained. Please explain the license choice in the commit " .
|
||||
"message.");
|
||||
}
|
||||
}
|
||||
|
||||
# All three of the methods below take a 'file info' record
|
||||
# which is a hash ref containing
|
||||
#
|
||||
# 'isgit': 1 if an enhanced git diff or 0 for a plain diff
|
||||
# 'githeader': 1 if still parsing git patch header, 0 otherwise
|
||||
# 'linestart': line number of start of file diff
|
||||
# 'lineend': line number of end of file diff
|
||||
# 'filenew': the new filename
|
||||
# 'fileold': the old filename (same as 'new filename' except
|
||||
# for renames in git diffs)
|
||||
# 'action': one of 'modified' (always) or 'new' or 'deleted' or
|
||||
# 'renamed' (git diffs only)
|
||||
# 'mode': file mode for new/deleted files (git diffs only)
|
||||
# 'similarity': file similarity when renamed (git diffs only)
|
||||
# 'facts': hash ref for storing any metadata related to checks
|
||||
#
|
||||
|
||||
# Called at the end of each patch, with the list of
|
||||
# real filenames that were seen in the patch
|
||||
sub process_file_list {
|
||||
my @fileinfos = @_;
|
||||
|
||||
# According to tests/qtest/bios-tables-test.c: do not
|
||||
# change expected file in the same commit with adding test
|
||||
my @acpi_testexpected;
|
||||
my @acpi_nontestexpected;
|
||||
|
||||
foreach my $fileinfo (@fileinfos) {
|
||||
# Note: shell script that rebuilds the expected files is in
|
||||
# the same directory as files themselves.
|
||||
# Note: allowed diff list can be changed both when changing
|
||||
# expected files and when changing tests.
|
||||
if ($fileinfo->{filenew} =~ m#^tests/data/acpi/# &&
|
||||
$fileinfo->{filenew} !~ m#^\.sh$#) {
|
||||
push @acpi_testexpected, $fileinfo->{filenew};
|
||||
} elsif ($fileinfo->{filenew} !~
|
||||
m#^tests/qtest/bios-tables-test-allowed-diff.h$#) {
|
||||
push @acpi_nontestexpected, $fileinfo->{filenew};
|
||||
}
|
||||
}
|
||||
if (int(@acpi_testexpected) > 0 and int(@acpi_nontestexpected) > 0) {
|
||||
ERROR("Do not add expected files together with tests, " .
|
||||
"follow instructions in " .
|
||||
"tests/qtest/bios-tables-test.c: both " .
|
||||
$$acpi_testexpected . " and " .
|
||||
$$acpi_nontestexpected . " found\n");
|
||||
"tests/qtest/bios-tables-test.c. Files\n\n " .
|
||||
join("\n ", @acpi_testexpected) .
|
||||
"\n\nand\n\n " .
|
||||
join("\n ", @acpi_nontestexpected) .
|
||||
"\n\nfound in the same patch\n");
|
||||
}
|
||||
|
||||
my $sawmaintainers = 0;
|
||||
my @maybemaintainers;
|
||||
foreach my $fileinfo (@fileinfos) {
|
||||
if ($fileinfo->{action} ne "modified" &&
|
||||
$fileinfo->{filenew} !~ m#^tests/data/acpi/#) {
|
||||
push @maybemaintainers, $fileinfo->{filenew};
|
||||
}
|
||||
if ($fileinfo->{filenew} eq "MAINTAINERS") {
|
||||
$sawmaintainers = 1;
|
||||
}
|
||||
}
|
||||
|
||||
# If we don't see a MAINTAINERS update, prod the user to check
|
||||
if (int(@maybemaintainers) > 0 && !$sawmaintainers) {
|
||||
WARN("added, moved or deleted file(s):\n\n " .
|
||||
join("\n ", @maybemaintainers) .
|
||||
"\n\nDoes MAINTAINERS need updating?\n");
|
||||
}
|
||||
}
|
||||
|
||||
# Called at the start of processing a diff hunk for a file
|
||||
sub process_start_of_file {
|
||||
my $fileinfo = shift;
|
||||
|
||||
# Check for incorrect file permissions
|
||||
if ($fileinfo->{action} eq "new" && ($fileinfo->{mode} & 0111)) {
|
||||
my $permhere = $fileinfo->{linestart} . "FILE: " .
|
||||
$fileinfo->{filenew} . "\n";
|
||||
if ($fileinfo->{filenew} =~
|
||||
/(\bMakefile.*|\.(c|cc|cpp|h|mak|s|S))$/) {
|
||||
ERROR("do not set execute permissions for source " .
|
||||
"files\n" . $permhere);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Called at the end of processing a diff hunk for a file
|
||||
sub process_end_of_file {
|
||||
my $fileinfo = shift;
|
||||
|
||||
if ($fileinfo->{action} eq "new" &&
|
||||
!exists $fileinfo->{facts}->{sawspdx}) {
|
||||
if ($fileinfo->{filenew} =~
|
||||
/(\.(c|h|py|pl|sh|json|inc|rs)|Makefile.*)$/) {
|
||||
# source code files MUST have SPDX license declared
|
||||
ERROR("New file '" . $fileinfo->{filenew} .
|
||||
"' requires 'SPDX-License-Identifier'");
|
||||
} else {
|
||||
# Other files MAY have SPDX license if appropriate
|
||||
WARN("Does new file '" . $fileinfo->{filenew} .
|
||||
"' need 'SPDX-License-Identifier'?");
|
||||
}
|
||||
}
|
||||
if ($fileinfo->{action} eq "new" &&
|
||||
exists $fileinfo->{facts}->{sawboilerplate}) {
|
||||
ERROR("New file '" . $fileinfo->{filenew} . "' must " .
|
||||
"not have license boilerplate header text, only " .
|
||||
"the SPDX-License-Identifier, unless this file was " .
|
||||
"copied from existing code already having such text.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1373,7 +1541,6 @@ sub process {
|
||||
|
||||
my $in_header_lines = $file ? 0 : 1;
|
||||
my $in_commit_log = 0; #Scanning lines before patch
|
||||
my $reported_maintainer_file = 0;
|
||||
my $reported_mixing_imported_file = 0;
|
||||
my $in_imported_file = 0;
|
||||
my $in_no_imported_file = 0;
|
||||
@@ -1389,7 +1556,10 @@ sub process {
|
||||
my $realfile = '';
|
||||
my $realline = 0;
|
||||
my $realcnt = 0;
|
||||
my $fileinfo;
|
||||
my @fileinfolist;
|
||||
my $here = '';
|
||||
my $oldhere = '';
|
||||
my $in_comment = 0;
|
||||
my $comment_edge = 0;
|
||||
my $first_line = 0;
|
||||
@@ -1402,9 +1572,6 @@ sub process {
|
||||
my %suppress_whiletrailers;
|
||||
my %suppress_export;
|
||||
|
||||
my $acpi_testexpected;
|
||||
my $acpi_nontestexpected;
|
||||
|
||||
# Pre-scan the patch sanitizing the lines.
|
||||
|
||||
sanitise_line_reset();
|
||||
@@ -1527,25 +1694,79 @@ sub process {
|
||||
$prefix = "$filename:$realline: " if ($emacs && $file);
|
||||
$prefix = "$filename:$linenr: " if ($emacs && !$file);
|
||||
|
||||
$oldhere = $here;
|
||||
$here = "#$linenr: " if (!$file);
|
||||
$here = "#$realline: " if ($file);
|
||||
|
||||
# extract the filename as it passes
|
||||
if ($line =~ /^diff --git.*?(\S+)$/) {
|
||||
$realfile = $1;
|
||||
$realfile =~ s@^([^/]*)/@@ if (!$file);
|
||||
checkfilename($realfile, \$acpi_testexpected, \$acpi_nontestexpected);
|
||||
if ($line =~ /^diff --git\s+(\S+)\s+(\S+)$/) {
|
||||
my $fileold = $1;
|
||||
my $filenew = $2;
|
||||
|
||||
if (defined $fileinfo) {
|
||||
$fileinfo->{lineend} = $oldhere;
|
||||
process_end_of_file($fileinfo)
|
||||
}
|
||||
$fileold =~ s@^([^/]*)/@@ if (!$file);
|
||||
$filenew =~ s@^([^/]*)/@@ if (!$file);
|
||||
$realfile = $filenew;
|
||||
|
||||
$fileinfo = {
|
||||
"isgit" => 1,
|
||||
"githeader" => 1,
|
||||
"linestart" => $here,
|
||||
"lineend" => 0,
|
||||
"fileold" => $fileold,
|
||||
"filenew" => $filenew,
|
||||
"action" => "modified",
|
||||
"mode" => 0,
|
||||
"similarity" => 0,
|
||||
"facts" => {},
|
||||
};
|
||||
push @fileinfolist, $fileinfo;
|
||||
} elsif (defined $fileinfo && $fileinfo->{githeader} &&
|
||||
$line =~ /^(new|deleted) (?:file )?mode\s+([0-7]+)$/) {
|
||||
$fileinfo->{action} = $1;
|
||||
$fileinfo->{mode} = oct($2);
|
||||
} elsif (defined $fileinfo && $fileinfo->{githeader} &&
|
||||
$line =~ /^similarity index (\d+)%/) {
|
||||
$fileinfo->{similarity} = int($1);
|
||||
} elsif (defined $fileinfo && $fileinfo->{githeader} &&
|
||||
$line =~ /^rename (from|to) [\w\/\.\-]+\s*$/) {
|
||||
$fileinfo->{action} = "renamed";
|
||||
# For a no-change rename, we'll never have any "+++..."
|
||||
# lines, so trigger actions now
|
||||
if ($1 eq "to" && $fileinfo->{similarity} == 100) {
|
||||
process_start_of_file($fileinfo);
|
||||
}
|
||||
} elsif ($line =~ /^\+\+\+\s+(\S+)/) {
|
||||
$realfile = $1;
|
||||
$realfile =~ s@^([^/]*)/@@ if (!$file);
|
||||
checkfilename($realfile, \$acpi_testexpected, \$acpi_nontestexpected);
|
||||
$realfile =~ s@^[^/]*/@@ if (!$file);
|
||||
|
||||
$p1_prefix = $1;
|
||||
if (!$file && $tree && $p1_prefix ne '' &&
|
||||
-e "$root/$p1_prefix") {
|
||||
WARN("patch prefix '$p1_prefix' exists, appears to be a -p0 patch\n");
|
||||
if (defined $fileinfo && !$fileinfo->{isgit}) {
|
||||
$fileinfo->{lineend} = $oldhere;
|
||||
process_end_of_file($fileinfo);
|
||||
}
|
||||
|
||||
if (!defined $fileinfo || !$fileinfo->{isgit}) {
|
||||
$fileinfo = {
|
||||
"isgit" => 0,
|
||||
"githeader" => 0,
|
||||
"linestart" => $here,
|
||||
"lineend" => 0,
|
||||
"fileold" => $realfile,
|
||||
"filenew" => $realfile,
|
||||
"action" => "modified",
|
||||
"mode" => 0,
|
||||
"similarity" => 0,
|
||||
"facts" => {},
|
||||
};
|
||||
push @fileinfolist, $fileinfo;
|
||||
} else {
|
||||
$fileinfo->{githeader} = 0;
|
||||
}
|
||||
process_start_of_file($fileinfo);
|
||||
|
||||
next;
|
||||
}
|
||||
|
||||
@@ -1557,14 +1778,6 @@ sub process {
|
||||
|
||||
$cnt_lines++ if ($realcnt != 0);
|
||||
|
||||
# Check for incorrect file permissions
|
||||
if ($line =~ /^new (file )?mode.*[7531]\d{0,2}$/) {
|
||||
my $permhere = $here . "FILE: $realfile\n";
|
||||
if ($realfile =~ /(\bMakefile(?:\.objs)?|\.c|\.cc|\.cpp|\.h|\.mak|\.[sS])$/) {
|
||||
ERROR("do not set execute permissions for source files\n" . $permhere);
|
||||
}
|
||||
}
|
||||
|
||||
# Only allow Python 3 interpreter
|
||||
if ($realline == 1 &&
|
||||
$line =~ /^\+#!\ *\/usr\/bin\/(?:env )?python$/) {
|
||||
@@ -1596,23 +1809,28 @@ sub process {
|
||||
}
|
||||
}
|
||||
|
||||
# Check if MAINTAINERS is being updated. If so, there's probably no need to
|
||||
# emit the "does MAINTAINERS need updating?" message on file add/move/delete
|
||||
if ($line =~ /^\s*MAINTAINERS\s*\|/) {
|
||||
$reported_maintainer_file = 1;
|
||||
# Check SPDX-License-Identifier references a permitted license
|
||||
if (($rawline =~ m,SPDX-License-Identifier: (.*?)(\*/)?\s*$,) &&
|
||||
$rawline !~ /^-/) {
|
||||
$fileinfo->{facts}->{sawspdx} = 1;
|
||||
&checkspdx($realfile, $1);
|
||||
}
|
||||
|
||||
# Check for added, moved or deleted files
|
||||
if (!$reported_maintainer_file && !$in_commit_log &&
|
||||
($line =~ /^(?:new|deleted) file mode\s*\d+\s*$/ ||
|
||||
$line =~ /^rename (?:from|to) [\w\/\.\-]+\s*$/ ||
|
||||
($line =~ /\{\s*([\w\/\.\-]*)\s*\=\>\s*([\w\/\.\-]*)\s*\}/ &&
|
||||
(defined($1) || defined($2)))) &&
|
||||
!(($realfile ne '') &&
|
||||
defined($acpi_testexpected) &&
|
||||
($realfile eq $acpi_testexpected))) {
|
||||
$reported_maintainer_file = 1;
|
||||
WARN("added, moved or deleted file(s), does MAINTAINERS need updating?\n" . $herecurr);
|
||||
if ($rawline =~ /$LICENSE_BOILERPLATE_RE/) {
|
||||
$fileinfo->{facts}->{sawboilerplate} = 1;
|
||||
}
|
||||
|
||||
if ($rawline =~ m,(SPDX-[a-zA-Z0-9-_]+):,) {
|
||||
my $tag = $1;
|
||||
my @permitted = qw(
|
||||
SPDX-License-Identifier
|
||||
);
|
||||
|
||||
unless (grep { /^$tag$/ } @permitted) {
|
||||
ERROR("Tag $tag not permitted in QEMU code, " .
|
||||
"valid choices are: " .
|
||||
join(", ", @permitted));
|
||||
}
|
||||
}
|
||||
|
||||
# Check for wrappage within a valid hunk of the file
|
||||
@@ -2193,7 +2411,7 @@ sub process {
|
||||
|
||||
# missing space after union, struct or enum definition
|
||||
if ($line =~ /^.\s*(?:typedef\s+)?(enum|union|struct)(?:\s+$Ident)?(?:\s+$Ident)?[=\{]/) {
|
||||
ERROR("missing space after $1 definition\n" . $herecurr);
|
||||
ERROR("missing space after $1 definition\n" . $herecurr);
|
||||
}
|
||||
|
||||
# check for spacing round square brackets; allowed:
|
||||
@@ -2488,7 +2706,7 @@ sub process {
|
||||
|
||||
if ($line =~ /^.\s*(Q(?:S?LIST|SIMPLEQ|TAILQ)_HEAD)\s*\(\s*[^,]/ &&
|
||||
$line !~ /^.typedef/) {
|
||||
ERROR("named $1 should be typedefed separately\n" . $herecurr);
|
||||
ERROR("named $1 should be typedefed separately\n" . $herecurr);
|
||||
}
|
||||
|
||||
# Need a space before open parenthesis after if, while etc
|
||||
@@ -2974,14 +3192,18 @@ sub process {
|
||||
if ($line =~ /\bsignal\s*\(/ && !($line =~ /SIG_(?:IGN|DFL)/)) {
|
||||
ERROR("use sigaction to establish signal handlers; signal is not portable\n" . $herecurr);
|
||||
}
|
||||
# recommend qemu_bh_new_guarded instead of qemu_bh_new
|
||||
if ($realfile =~ /.*\/hw\/.*/ && $line =~ /\bqemu_bh_new\s*\(/) {
|
||||
ERROR("use qemu_bh_new_guarded() instead of qemu_bh_new() to avoid reentrancy problems\n" . $herecurr);
|
||||
# recommend aio_bh_new_guarded instead of legacy qemu_bh_new / qemu_bh_new_guarded
|
||||
if ($realfile =~ /.*\/hw\/.*/ && $line =~ /\bqemu_bh_new(_guarded)?\s*\(/) {
|
||||
ERROR("use aio_bh_new_guarded() instead of qemu_bh_new*() to avoid reentrancy problems\n" . $herecurr);
|
||||
}
|
||||
# recommend aio_bh_new_guarded instead of aio_bh_new
|
||||
if ($realfile =~ /.*\/hw\/.*/ && $line =~ /\baio_bh_new\s*\(/) {
|
||||
ERROR("use aio_bh_new_guarded() instead of aio_bh_new() to avoid reentrancy problems\n" . $herecurr);
|
||||
}
|
||||
# check for DEVICE_NATIVE_ENDIAN, use explicit endianness instead
|
||||
if ($line =~ /\bDEVICE_NATIVE_ENDIAN\b/) {
|
||||
ERROR("DEVICE_NATIVE_ENDIAN is not allowed, use DEVICE_LITTLE_ENDIAN or DEVICE_BIG_ENDIAN instead\n" . $herecurr);
|
||||
}
|
||||
# check for module_init(), use category-specific init macros explicitly please
|
||||
if ($line =~ /^module_init\s*\(/) {
|
||||
ERROR("please use block_init(), type_init() etc. instead of module_init()\n" . $herecurr);
|
||||
@@ -3037,48 +3259,50 @@ sub process {
|
||||
|
||||
# Qemu error function tests
|
||||
|
||||
# Find newlines in error messages
|
||||
my $qemu_error_funcs = qr{error_setg|
|
||||
error_setg_errno|
|
||||
error_setg_win32|
|
||||
error_setg_file_open|
|
||||
error_set|
|
||||
error_prepend|
|
||||
warn_reportf_err|
|
||||
error_reportf_err|
|
||||
error_vreport|
|
||||
warn_vreport|
|
||||
info_vreport|
|
||||
error_report|
|
||||
warn_report|
|
||||
info_report|
|
||||
g_test_message}x;
|
||||
# Find newlines in error messages
|
||||
my $qemu_error_funcs = qr{error_setg|
|
||||
error_setg_errno|
|
||||
error_setg_win32|
|
||||
error_setg_file_open|
|
||||
error_set|
|
||||
error_prepend|
|
||||
warn_reportf_err|
|
||||
error_reportf_err|
|
||||
error_vreport|
|
||||
warn_vreport|
|
||||
info_vreport|
|
||||
error_report|
|
||||
warn_report|
|
||||
info_report|
|
||||
g_test_message}x;
|
||||
|
||||
if ($rawline =~ /\b(?:$qemu_error_funcs)\s*\(.*\".*\\n/) {
|
||||
ERROR("Error messages should not contain newlines\n" . $herecurr);
|
||||
}
|
||||
|
||||
# Continue checking for error messages that contains newlines. This
|
||||
# check handles cases where string literals are spread over multiple lines.
|
||||
# Example:
|
||||
# error_report("Error msg line #1"
|
||||
# "Error msg line #2\n");
|
||||
my $quoted_newline_regex = qr{\+\s*\".*\\n.*\"};
|
||||
my $continued_str_literal = qr{\+\s*\".*\"};
|
||||
|
||||
if ($rawline =~ /$quoted_newline_regex/) {
|
||||
# Backtrack to first line that does not contain only a quoted literal
|
||||
# and assume that it is the start of the statement.
|
||||
my $i = $linenr - 2;
|
||||
|
||||
while (($i >= 0) & $rawlines[$i] =~ /$continued_str_literal/) {
|
||||
$i--;
|
||||
}
|
||||
|
||||
if ($rawlines[$i] =~ /\b(?:$qemu_error_funcs)\s*\(/) {
|
||||
if ($rawline =~ /\b(?:$qemu_error_funcs)\s*\(.*\".*\\n/) {
|
||||
ERROR("Error messages should not contain newlines\n" . $herecurr);
|
||||
}
|
||||
}
|
||||
|
||||
# Continue checking for error messages that contains newlines.
|
||||
# This check handles cases where string literals are spread
|
||||
# over multiple lines.
|
||||
# Example:
|
||||
# error_report("Error msg line #1"
|
||||
# "Error msg line #2\n");
|
||||
my $quoted_newline_regex = qr{\+\s*\".*\\n.*\"};
|
||||
my $continued_str_literal = qr{\+\s*\".*\"};
|
||||
|
||||
if ($rawline =~ /$quoted_newline_regex/) {
|
||||
# Backtrack to first line that does not contain only
|
||||
# a quoted literal and assume that it is the start
|
||||
# of the statement.
|
||||
my $i = $linenr - 2;
|
||||
|
||||
while (($i >= 0) & $rawlines[$i] =~ /$continued_str_literal/) {
|
||||
$i--;
|
||||
}
|
||||
|
||||
if ($rawlines[$i] =~ /\b(?:$qemu_error_funcs)\s*\(/) {
|
||||
ERROR("Error messages should not contain newlines\n" . $herecurr);
|
||||
}
|
||||
}
|
||||
|
||||
# check for non-portable libc calls that have portable alternatives in QEMU
|
||||
if ($line =~ /\bffs\(/) {
|
||||
@@ -3132,6 +3356,11 @@ sub process {
|
||||
}
|
||||
}
|
||||
|
||||
if (defined $fileinfo) {
|
||||
process_end_of_file($fileinfo);
|
||||
}
|
||||
process_file_list(@fileinfolist);
|
||||
|
||||
if ($is_patch && $chk_signoff && $signoff == 0) {
|
||||
ERROR("Missing Signed-off-by: line(s)\n");
|
||||
}
|
||||
|
||||
Executable
+117
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# A script to analyse failures in the gitlab pipelines. It requires an
|
||||
# API key from gitlab with the following permissions:
|
||||
# - api
|
||||
# - read_repository
|
||||
# - read_user
|
||||
#
|
||||
|
||||
import argparse
|
||||
import gitlab
|
||||
import os
|
||||
|
||||
#
|
||||
# Arguments
|
||||
#
|
||||
class NoneForEmptyStringAction(argparse.Action):
|
||||
def __call__(self, parser, namespace, value, option_string=None):
|
||||
if value == '':
|
||||
setattr(namespace, self.dest, None)
|
||||
else:
|
||||
setattr(namespace, self.dest, value)
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description="Analyse failed GitLab CI runs.")
|
||||
|
||||
parser.add_argument("--gitlab",
|
||||
default="https://gitlab.com",
|
||||
help="GitLab instance URL (default: https://gitlab.com).")
|
||||
parser.add_argument("--id", default=11167699,
|
||||
type=int,
|
||||
help="GitLab project id (default: 11167699 for qemu-project/qemu)")
|
||||
parser.add_argument("--token",
|
||||
default=os.getenv("GITLAB_TOKEN"),
|
||||
help="Your personal access token with 'api' scope.")
|
||||
parser.add_argument("--branch",
|
||||
type=str,
|
||||
default="staging",
|
||||
action=NoneForEmptyStringAction,
|
||||
help="The name of the branch (default: 'staging')")
|
||||
parser.add_argument("--status",
|
||||
type=str,
|
||||
action=NoneForEmptyStringAction,
|
||||
default="failed",
|
||||
help="Filter by branch status (default: 'failed')")
|
||||
parser.add_argument("--count", type=int,
|
||||
default=3,
|
||||
help="The number of failed runs to fetch.")
|
||||
parser.add_argument("--skip-jobs",
|
||||
default=False,
|
||||
action='store_true',
|
||||
help="Skip dumping the job info")
|
||||
parser.add_argument("--pipeline", type=int,
|
||||
nargs="+",
|
||||
default=None,
|
||||
help="Explicit pipeline ID(s) to fetch.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parser.parse_args()
|
||||
|
||||
gl = gitlab.Gitlab(url=args.gitlab, private_token=args.token)
|
||||
project = gl.projects.get(args.id)
|
||||
|
||||
|
||||
pipelines_to_process = []
|
||||
|
||||
# Use explicit pipeline IDs if provided, otherwise fetch a list
|
||||
if args.pipeline:
|
||||
args.count = len(args.pipeline)
|
||||
for p_id in args.pipeline:
|
||||
pipelines_to_process.append(project.pipelines.get(p_id))
|
||||
else:
|
||||
# Use an iterator to fetch the pipelines
|
||||
pipe_iter = project.pipelines.list(iterator=True,
|
||||
status=args.status,
|
||||
ref=args.branch)
|
||||
# Check each failed pipeline
|
||||
pipelines_to_process = [next(pipe_iter) for _ in range(args.count)]
|
||||
|
||||
# Check each pipeline
|
||||
for p in pipelines_to_process:
|
||||
|
||||
jobs = p.jobs.list(get_all=True)
|
||||
failed_jobs = [j for j in jobs if j.status == "failed"]
|
||||
skipped_jobs = [j for j in jobs if j.status == "skipped"]
|
||||
manual_jobs = [j for j in jobs if j.status == "manual"]
|
||||
|
||||
trs = p.test_report_summary.get()
|
||||
total = trs.total["count"]
|
||||
skipped = trs.total["skipped"]
|
||||
failed = trs.total["failed"]
|
||||
|
||||
print(f"{p.status} pipeline {p.id}, total jobs {len(jobs)}, "
|
||||
f"skipped {len(skipped_jobs)}, "
|
||||
f"failed {len(failed_jobs)}, ",
|
||||
f"{total} tests, "
|
||||
f"{skipped} skipped tests, "
|
||||
f"{failed} failed tests")
|
||||
|
||||
if not args.skip_jobs:
|
||||
for j in failed_jobs:
|
||||
print(f" Failed job {j.id}, {j.name}, {j.web_url}")
|
||||
|
||||
# It seems we can only extract failing tests from the full
|
||||
# test report, maybe there is some way to filter it.
|
||||
|
||||
if failed > 0:
|
||||
ftr = p.test_report.get()
|
||||
failed_suites = [s for s in ftr.test_suites if
|
||||
s["failed_count"] > 0]
|
||||
for fs in failed_suites:
|
||||
name = fs["name"]
|
||||
tests = fs["test_cases"]
|
||||
failed_tests = [t for t in tests if t["status"] == 'failed']
|
||||
for t in failed_tests:
|
||||
print(f" Failed test {t["classname"]}, {name}, {t["name"]}")
|
||||
@@ -0,0 +1,56 @@
|
||||
# Copyright (c) 2021 Red Hat, Inc.
|
||||
#
|
||||
# Author:
|
||||
# Cleber Rosa <crosa@redhat.com>
|
||||
#
|
||||
# This work is licensed under the terms of the GNU GPL, version 2 or
|
||||
# later. See the COPYING file in the top-level directory.
|
||||
#
|
||||
# This is an ansible playbook file. Run it to set up systems with the
|
||||
# environment needed to build QEMU.
|
||||
---
|
||||
- name: Installation of basic packages to build QEMU
|
||||
hosts: all
|
||||
tasks:
|
||||
- name: Check for suitable ansible version
|
||||
delegate_to: localhost
|
||||
assert:
|
||||
that:
|
||||
- '((ansible_version.major == 2) and (ansible_version.minor >= 8)) or (ansible_version.major >= 3)'
|
||||
msg: "Unsuitable ansible version, please use version 2.8.0 or later"
|
||||
|
||||
- name: Update apt cache / upgrade packages via apt
|
||||
apt:
|
||||
update_cache: yes
|
||||
upgrade: yes
|
||||
when:
|
||||
- ansible_facts['distribution'] in ['Ubuntu', 'Debian']
|
||||
|
||||
# the package lists are updated by "make lcitool-refresh"
|
||||
- name: Define package list file path for Ubuntu
|
||||
set_fact:
|
||||
package_file: "ubuntu/ubuntu-2404-{{ ansible_facts['architecture'] }}.yaml"
|
||||
when:
|
||||
- ansible_facts['distribution'] == 'Ubuntu'
|
||||
- ansible_facts['distribution_version'] == '24.04'
|
||||
|
||||
- name: Define package list file path for Debian
|
||||
set_fact:
|
||||
package_file: "debian/debian-{{ ansible_facts['distribution_major_version'] }}-{{ ansible_facts['architecture'] }}.yaml"
|
||||
when:
|
||||
- ansible_facts['distribution'] == 'Debian'
|
||||
|
||||
- name: Include package lists based on OS and architecture
|
||||
include_vars:
|
||||
file: "{{ package_file }}"
|
||||
when:
|
||||
- package_file is exists
|
||||
|
||||
- name: Install packages for QEMU on Ubuntu/Debian
|
||||
package:
|
||||
name: "{{ packages }}"
|
||||
when:
|
||||
- package_file is exists
|
||||
- packages is defined
|
||||
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
# THIS FILE WAS AUTO-GENERATED
|
||||
#
|
||||
# $ lcitool variables --host-arch ppc64le debian-13 qemu
|
||||
#
|
||||
# https://gitlab.com/libvirt/libvirt-ci
|
||||
|
||||
packages:
|
||||
- bash
|
||||
- bc
|
||||
- bindgen
|
||||
- bison
|
||||
- bsdextrautils
|
||||
- bzip2
|
||||
- ca-certificates
|
||||
- ccache
|
||||
- clang
|
||||
- coreutils
|
||||
- dbus
|
||||
- debianutils
|
||||
- diffutils
|
||||
- exuberant-ctags
|
||||
- findutils
|
||||
- flex
|
||||
- gcc
|
||||
- gcovr
|
||||
- gettext
|
||||
- git
|
||||
- hostname
|
||||
- libaio-dev
|
||||
- libasan8
|
||||
- libasound2-dev
|
||||
- libattr1-dev
|
||||
- libbpf-dev
|
||||
- libbrlapi-dev
|
||||
- libbz2-dev
|
||||
- libc6-dev
|
||||
- libcacard-dev
|
||||
- libcap-ng-dev
|
||||
- libcapstone-dev
|
||||
- libcbor-dev
|
||||
- libclang-rt-dev
|
||||
- libcmocka-dev
|
||||
- libcurl4-gnutls-dev
|
||||
- libdaxctl-dev
|
||||
- libdrm-dev
|
||||
- libepoxy-dev
|
||||
- libfdt-dev
|
||||
- libffi-dev
|
||||
- libfuse3-dev
|
||||
- libgbm-dev
|
||||
- libgcrypt20-dev
|
||||
- libglib2.0-dev
|
||||
- libglusterfs-dev
|
||||
- libgnutls28-dev
|
||||
- libgtk-3-dev
|
||||
- libgtk-vnc-2.0-dev
|
||||
- libibverbs-dev
|
||||
- libiscsi-dev
|
||||
- libjemalloc-dev
|
||||
- libjpeg62-turbo-dev
|
||||
- libjson-c-dev
|
||||
- liblttng-ust-dev
|
||||
- liblzo2-dev
|
||||
- libncursesw5-dev
|
||||
- libnfs-dev
|
||||
- libnuma-dev
|
||||
- libpam0g-dev
|
||||
- libpcre2-dev
|
||||
- libpipewire-0.3-dev
|
||||
- libpixman-1-dev
|
||||
- libpng-dev
|
||||
- libpulse-dev
|
||||
- librbd-dev
|
||||
- librdmacm-dev
|
||||
- libsasl2-dev
|
||||
- libsdl2-dev
|
||||
- libsdl2-image-dev
|
||||
- libseccomp-dev
|
||||
- libselinux1-dev
|
||||
- libslirp-dev
|
||||
- libsnappy-dev
|
||||
- libsndio-dev
|
||||
- libspice-protocol-dev
|
||||
- libspice-server-dev
|
||||
- libssh-dev
|
||||
- libstd-rust-dev
|
||||
- libsystemd-dev
|
||||
- libtasn1-6-dev
|
||||
- libubsan1
|
||||
- libudev-dev
|
||||
- liburing-dev
|
||||
- libusb-1.0-0-dev
|
||||
- libusbredirhost-dev
|
||||
- libvdeplug-dev
|
||||
- libvirglrenderer-dev
|
||||
- libvte-2.91-dev
|
||||
- libxdp-dev
|
||||
- libzstd-dev
|
||||
- llvm
|
||||
- locales
|
||||
- make
|
||||
- mtools
|
||||
- multipath-tools
|
||||
- ncat
|
||||
- nettle-dev
|
||||
- ninja-build
|
||||
- openssh-client
|
||||
- pkgconf
|
||||
- python3
|
||||
- python3-numpy
|
||||
- python3-opencv
|
||||
- python3-pillow
|
||||
- python3-pip
|
||||
- python3-setuptools
|
||||
- python3-sphinx
|
||||
- python3-sphinx-rtd-theme
|
||||
- python3-tomli
|
||||
- python3-venv
|
||||
- python3-wheel
|
||||
- python3-yaml
|
||||
- rpm2cpio
|
||||
- rustc
|
||||
- sed
|
||||
- socat
|
||||
- sparse
|
||||
- swtpm
|
||||
- systemtap-sdt-dev
|
||||
- tar
|
||||
- tesseract-ocr
|
||||
- tesseract-ocr-eng
|
||||
- vulkan-tools
|
||||
- xorriso
|
||||
- zlib1g-dev
|
||||
- zstd
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
tasks:
|
||||
- debug:
|
||||
msg: 'Checking for a valid GitLab registration token'
|
||||
failed_when: "gitlab_runner_registration_token == 'PLEASE_PROVIDE_A_VALID_TOKEN'"
|
||||
failed_when: "gitlab_runner_authentication_token == 'PLEASE_PROVIDE_A_VALID_TOKEN'"
|
||||
|
||||
- name: Create a group for the gitlab-runner service
|
||||
group:
|
||||
@@ -56,12 +56,12 @@
|
||||
url: "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh"
|
||||
mode: 0755
|
||||
when:
|
||||
- ansible_facts['distribution'] == 'Ubuntu'
|
||||
- ansible_facts['distribution'] in ['Ubuntu', 'Debian']
|
||||
|
||||
- name: Run gitlab-runner repo setup script (DEB)
|
||||
shell: "/root/script.deb.sh"
|
||||
when:
|
||||
- ansible_facts['distribution'] == 'Ubuntu'
|
||||
- ansible_facts['distribution'] in ['Ubuntu', 'Debian']
|
||||
|
||||
- name: Install gitlab-runner (DEB)
|
||||
ansible.builtin.apt:
|
||||
@@ -69,7 +69,7 @@
|
||||
update_cache: yes
|
||||
state: present
|
||||
when:
|
||||
- ansible_facts['distribution'] == 'Ubuntu'
|
||||
- ansible_facts['distribution'] in ['Ubuntu', 'Debian']
|
||||
|
||||
# RPM setup
|
||||
- name: Get gitlab-runner repo setup script (RPM)
|
||||
@@ -95,15 +95,7 @@
|
||||
|
||||
# Register Runners
|
||||
- name: Register the gitlab-runner
|
||||
command: "/usr/bin/gitlab-runner register --non-interactive --url {{ gitlab_runner_server_url }} --registration-token {{ gitlab_runner_registration_token }} --executor shell --tag-list {{ ansible_facts[\"architecture\"] }},{{ ansible_facts[\"distribution\"]|lower }}_{{ ansible_facts[\"distribution_version\"] }} --description '{{ ansible_facts[\"distribution\"] }} {{ ansible_facts[\"distribution_version\"] }} {{ ansible_facts[\"architecture\"] }} ({{ ansible_facts[\"os_family\"] }})'"
|
||||
|
||||
# The secondary runner will still run under the single gitlab-runner service
|
||||
- name: Register secondary gitlab-runner
|
||||
command: "/usr/bin/gitlab-runner register --non-interactive --url {{ gitlab_runner_server_url }} --registration-token {{ gitlab_runner_registration_token }} --executor shell --tag-list aarch32,{{ ansible_facts[\"distribution\"]|lower }}_{{ ansible_facts[\"distribution_version\"] }} --description '{{ ansible_facts[\"distribution\"] }} {{ ansible_facts[\"distribution_version\"] }} {{ ansible_facts[\"architecture\"] }} ({{ ansible_facts[\"os_family\"] }})'"
|
||||
when:
|
||||
- ansible_facts['distribution'] == 'Ubuntu'
|
||||
- ansible_facts['architecture'] == 'aarch64'
|
||||
- ansible_facts['distribution_version'] == '22.04'
|
||||
command: "/usr/bin/gitlab-runner register --non-interactive --url {{ gitlab_runner_server_url }} --token {{ gitlab_runner_authentication_token }} --executor shell"
|
||||
|
||||
- name: Install the gitlab-runner service using its own functionality
|
||||
command: "/usr/bin/gitlab-runner install --user gitlab-runner --working-directory /home/gitlab-runner"
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
# Copyright (c) 2021 Red Hat, Inc.
|
||||
#
|
||||
# Author:
|
||||
# Cleber Rosa <crosa@redhat.com>
|
||||
#
|
||||
# This work is licensed under the terms of the GNU GPL, version 2 or
|
||||
# later. See the COPYING file in the top-level directory.
|
||||
#
|
||||
# This is an ansible playbook file. Run it to set up systems with the
|
||||
# environment needed to build QEMU.
|
||||
---
|
||||
- name: Installation of basic packages to build QEMU
|
||||
hosts: all
|
||||
tasks:
|
||||
- name: Check for suitable ansible version
|
||||
delegate_to: localhost
|
||||
assert:
|
||||
that:
|
||||
- '((ansible_version.major == 2) and (ansible_version.minor >= 8)) or (ansible_version.major >= 3)'
|
||||
msg: "Unsuitable ansible version, please use version 2.8.0 or later"
|
||||
|
||||
- name: Add armhf foreign architecture to aarch64 hosts
|
||||
command: dpkg --add-architecture armhf
|
||||
when:
|
||||
- ansible_facts['distribution'] == 'Ubuntu'
|
||||
- ansible_facts['architecture'] == 'aarch64'
|
||||
|
||||
- name: Update apt cache / upgrade packages via apt
|
||||
apt:
|
||||
update_cache: yes
|
||||
upgrade: yes
|
||||
when:
|
||||
- ansible_facts['distribution'] == 'Ubuntu'
|
||||
|
||||
# the package lists are updated by "make lcitool-refresh"
|
||||
- name: Include package lists based on OS and architecture
|
||||
include_vars:
|
||||
file: "ubuntu-2204-{{ ansible_facts['architecture'] }}.yaml"
|
||||
when:
|
||||
- ansible_facts['distribution'] == 'Ubuntu'
|
||||
- ansible_facts['distribution_version'] == '22.04'
|
||||
|
||||
- name: Install packages for QEMU on Ubuntu 22.04
|
||||
package:
|
||||
name: "{{ packages }}"
|
||||
when:
|
||||
- ansible_facts['distribution'] == 'Ubuntu'
|
||||
- ansible_facts['distribution_version'] == '22.04'
|
||||
|
||||
- name: Install armhf cross-compile packages to build QEMU on AArch64 Ubuntu 22.04
|
||||
package:
|
||||
name:
|
||||
- binutils-arm-linux-gnueabihf
|
||||
- gcc-arm-linux-gnueabihf
|
||||
- libblkid-dev:armhf
|
||||
- libc6-dev:armhf
|
||||
- libffi-dev:armhf
|
||||
- libglib2.0-dev:armhf
|
||||
- libmount-dev:armhf
|
||||
- libpcre2-dev:armhf
|
||||
- libpixman-1-dev:armhf
|
||||
- zlib1g-dev:armhf
|
||||
when:
|
||||
- ansible_facts['distribution'] == 'Ubuntu'
|
||||
- ansible_facts['distribution_version'] == '22.04'
|
||||
- ansible_facts['architecture'] == 'aarch64'
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
# THIS FILE WAS AUTO-GENERATED
|
||||
#
|
||||
# $ lcitool variables --cross-arch armv7l ubuntu-2204 qemu
|
||||
#
|
||||
# https://gitlab.com/libvirt/libvirt-ci
|
||||
|
||||
packages:
|
||||
- bash
|
||||
- bc
|
||||
- bison
|
||||
- bsdextrautils
|
||||
- bzip2
|
||||
- ca-certificates
|
||||
- ccache
|
||||
- dbus
|
||||
- debianutils
|
||||
- diffutils
|
||||
- exuberant-ctags
|
||||
- findutils
|
||||
- flex
|
||||
- gcc
|
||||
- gcovr
|
||||
- gettext
|
||||
- git
|
||||
- hostname
|
||||
- libglib2.0-dev
|
||||
- libpcre2-dev
|
||||
- libsndio-dev
|
||||
- libspice-protocol-dev
|
||||
- llvm
|
||||
- locales
|
||||
- make
|
||||
- meson
|
||||
- mtools
|
||||
- ncat
|
||||
- ninja-build
|
||||
- openssh-client
|
||||
- pkgconf
|
||||
- python3
|
||||
- python3-numpy
|
||||
- python3-opencv
|
||||
- python3-pillow
|
||||
- python3-pip
|
||||
- python3-sphinx
|
||||
- python3-sphinx-rtd-theme
|
||||
- python3-tomli
|
||||
- python3-venv
|
||||
- python3-yaml
|
||||
- rpm2cpio
|
||||
- sed
|
||||
- socat
|
||||
- sparse
|
||||
- swtpm
|
||||
- tar
|
||||
- tesseract-ocr
|
||||
- tesseract-ocr-eng
|
||||
- xorriso
|
||||
- zstd
|
||||
- gcc-arm-linux-gnueabihf
|
||||
- libaio-dev:armhf
|
||||
- libasan6:armhf
|
||||
- libasound2-dev:armhf
|
||||
- libattr1-dev:armhf
|
||||
- libbpf-dev:armhf
|
||||
- libbrlapi-dev:armhf
|
||||
- libbz2-dev:armhf
|
||||
- libc6-dev:armhf
|
||||
- libcacard-dev:armhf
|
||||
- libcap-ng-dev:armhf
|
||||
- libcapstone-dev:armhf
|
||||
- libcmocka-dev:armhf
|
||||
- libcurl4-gnutls-dev:armhf
|
||||
- libdaxctl-dev:armhf
|
||||
- libdrm-dev:armhf
|
||||
- libepoxy-dev:armhf
|
||||
- libfdt-dev:armhf
|
||||
- libffi-dev:armhf
|
||||
- libfuse3-dev:armhf
|
||||
- libgbm-dev:armhf
|
||||
- libgcrypt20-dev:armhf
|
||||
- libglib2.0-dev:armhf
|
||||
- libglusterfs-dev:armhf
|
||||
- libgnutls28-dev:armhf
|
||||
- libgtk-3-dev:armhf
|
||||
- libibumad-dev:armhf
|
||||
- libibverbs-dev:armhf
|
||||
- libiscsi-dev:armhf
|
||||
- libjemalloc-dev:armhf
|
||||
- libjpeg-turbo8-dev:armhf
|
||||
- libjson-c-dev:armhf
|
||||
- liblttng-ust-dev:armhf
|
||||
- liblzo2-dev:armhf
|
||||
- libncursesw5-dev:armhf
|
||||
- libnfs-dev:armhf
|
||||
- libnuma-dev:armhf
|
||||
- libpam0g-dev:armhf
|
||||
- libpipewire-0.3-dev:armhf
|
||||
- libpixman-1-dev:armhf
|
||||
- libpng-dev:armhf
|
||||
- libpulse-dev:armhf
|
||||
- librbd-dev:armhf
|
||||
- librdmacm-dev:armhf
|
||||
- libsasl2-dev:armhf
|
||||
- libsdl2-dev:armhf
|
||||
- libsdl2-image-dev:armhf
|
||||
- libseccomp-dev:armhf
|
||||
- libselinux1-dev:armhf
|
||||
- libslirp-dev:armhf
|
||||
- libsnappy-dev:armhf
|
||||
- libspice-server-dev:armhf
|
||||
- libssh-dev:armhf
|
||||
- libsystemd-dev:armhf
|
||||
- libtasn1-6-dev:armhf
|
||||
- libubsan1:armhf
|
||||
- libudev-dev:armhf
|
||||
- liburing-dev:armhf
|
||||
- libusb-1.0-0-dev:armhf
|
||||
- libusbredirhost-dev:armhf
|
||||
- libvdeplug-dev:armhf
|
||||
- libvirglrenderer-dev:armhf
|
||||
- libvte-2.91-dev:armhf
|
||||
- libxen-dev:armhf
|
||||
- libzstd-dev:armhf
|
||||
- nettle-dev:armhf
|
||||
- systemtap-sdt-dev:armhf
|
||||
- zlib1g-dev:armhf
|
||||
|
||||
+11
-4
@@ -1,18 +1,20 @@
|
||||
# THIS FILE WAS AUTO-GENERATED
|
||||
#
|
||||
# $ lcitool variables --host-arch aarch64 ubuntu-2204 qemu
|
||||
# $ lcitool variables --host-arch aarch64 ubuntu-2404 qemu
|
||||
#
|
||||
# https://gitlab.com/libvirt/libvirt-ci
|
||||
|
||||
packages:
|
||||
- bash
|
||||
- bc
|
||||
- bindgen
|
||||
- bison
|
||||
- bsdextrautils
|
||||
- bzip2
|
||||
- ca-certificates
|
||||
- ccache
|
||||
- clang
|
||||
- coreutils
|
||||
- dbus
|
||||
- debianutils
|
||||
- diffutils
|
||||
@@ -25,7 +27,7 @@ packages:
|
||||
- git
|
||||
- hostname
|
||||
- libaio-dev
|
||||
- libasan6
|
||||
- libasan8
|
||||
- libasound2-dev
|
||||
- libattr1-dev
|
||||
- libbpf-dev
|
||||
@@ -36,6 +38,7 @@ packages:
|
||||
- libcap-ng-dev
|
||||
- libcapstone-dev
|
||||
- libcbor-dev
|
||||
- libclang-rt-dev
|
||||
- libcmocka-dev
|
||||
- libcurl4-gnutls-dev
|
||||
- libdaxctl-dev
|
||||
@@ -80,6 +83,7 @@ packages:
|
||||
- libspice-protocol-dev
|
||||
- libspice-server-dev
|
||||
- libssh-dev
|
||||
- libstd-rust-dev
|
||||
- libsystemd-dev
|
||||
- libtasn1-6-dev
|
||||
- libubsan1
|
||||
@@ -90,12 +94,12 @@ packages:
|
||||
- libvdeplug-dev
|
||||
- libvirglrenderer-dev
|
||||
- libvte-2.91-dev
|
||||
- libxdp-dev
|
||||
- libxen-dev
|
||||
- libzstd-dev
|
||||
- llvm
|
||||
- locales
|
||||
- make
|
||||
- meson
|
||||
- mtools
|
||||
- multipath-tools
|
||||
- ncat
|
||||
@@ -108,13 +112,15 @@ packages:
|
||||
- python3-opencv
|
||||
- python3-pillow
|
||||
- python3-pip
|
||||
- python3-setuptools
|
||||
- python3-sphinx
|
||||
- python3-sphinx-rtd-theme
|
||||
- python3-tomli
|
||||
- python3-venv
|
||||
- python3-wheel
|
||||
- python3-yaml
|
||||
- rpm2cpio
|
||||
- rustc
|
||||
- rustc-1.83
|
||||
- sed
|
||||
- socat
|
||||
- sparse
|
||||
@@ -123,6 +129,7 @@ packages:
|
||||
- tar
|
||||
- tesseract-ocr
|
||||
- tesseract-ocr-eng
|
||||
- vulkan-tools
|
||||
- xorriso
|
||||
- zlib1g-dev
|
||||
- zstd
|
||||
+11
-4
@@ -1,18 +1,20 @@
|
||||
# THIS FILE WAS AUTO-GENERATED
|
||||
#
|
||||
# $ lcitool variables --host-arch s390x ubuntu-2204 qemu
|
||||
# $ lcitool variables --host-arch s390x ubuntu-2404 qemu
|
||||
#
|
||||
# https://gitlab.com/libvirt/libvirt-ci
|
||||
|
||||
packages:
|
||||
- bash
|
||||
- bc
|
||||
- bindgen
|
||||
- bison
|
||||
- bsdextrautils
|
||||
- bzip2
|
||||
- ca-certificates
|
||||
- ccache
|
||||
- clang
|
||||
- coreutils
|
||||
- dbus
|
||||
- debianutils
|
||||
- diffutils
|
||||
@@ -25,7 +27,7 @@ packages:
|
||||
- git
|
||||
- hostname
|
||||
- libaio-dev
|
||||
- libasan6
|
||||
- libasan8
|
||||
- libasound2-dev
|
||||
- libattr1-dev
|
||||
- libbpf-dev
|
||||
@@ -36,6 +38,7 @@ packages:
|
||||
- libcap-ng-dev
|
||||
- libcapstone-dev
|
||||
- libcbor-dev
|
||||
- libclang-rt-dev
|
||||
- libcmocka-dev
|
||||
- libcurl4-gnutls-dev
|
||||
- libdaxctl-dev
|
||||
@@ -79,6 +82,7 @@ packages:
|
||||
- libsndio-dev
|
||||
- libspice-protocol-dev
|
||||
- libssh-dev
|
||||
- libstd-rust-dev
|
||||
- libsystemd-dev
|
||||
- libtasn1-6-dev
|
||||
- libubsan1
|
||||
@@ -89,11 +93,11 @@ packages:
|
||||
- libvdeplug-dev
|
||||
- libvirglrenderer-dev
|
||||
- libvte-2.91-dev
|
||||
- libxdp-dev
|
||||
- libzstd-dev
|
||||
- llvm
|
||||
- locales
|
||||
- make
|
||||
- meson
|
||||
- mtools
|
||||
- multipath-tools
|
||||
- ncat
|
||||
@@ -106,13 +110,15 @@ packages:
|
||||
- python3-opencv
|
||||
- python3-pillow
|
||||
- python3-pip
|
||||
- python3-setuptools
|
||||
- python3-sphinx
|
||||
- python3-sphinx-rtd-theme
|
||||
- python3-tomli
|
||||
- python3-venv
|
||||
- python3-wheel
|
||||
- python3-yaml
|
||||
- rpm2cpio
|
||||
- rustc
|
||||
- rustc-1.83
|
||||
- sed
|
||||
- socat
|
||||
- sparse
|
||||
@@ -121,6 +127,7 @@ packages:
|
||||
- tar
|
||||
- tesseract-ocr
|
||||
- tesseract-ocr-eng
|
||||
- vulkan-tools
|
||||
- xorriso
|
||||
- zlib1g-dev
|
||||
- zstd
|
||||
@@ -6,5 +6,6 @@ ansible_to_gitlab_arch:
|
||||
x86_64: amd64
|
||||
aarch64: arm64
|
||||
s390x: s390x
|
||||
# A unique token made available by GitLab to your project for registering runners
|
||||
gitlab_runner_registration_token: PLEASE_PROVIDE_A_VALID_TOKEN
|
||||
# A unique token made obtained from GitLab for each runner
|
||||
# see: https://gitlab.com/PROJECT/REPO/-/runners/new
|
||||
gitlab_runner_authentication_token: PLEASE_PROVIDE_A_VALID_TOKEN
|
||||
|
||||
@@ -130,8 +130,8 @@ for f in "$@"; do
|
||||
*include/qemu/compiler.h | \
|
||||
*include/qemu/qemu-plugin.h | \
|
||||
*include/glib-compat.h | \
|
||||
*include/sysemu/os-posix.h | \
|
||||
*include/sysemu/os-win32.h | \
|
||||
*include/system/os-posix.h | \
|
||||
*include/system/os-win32.h | \
|
||||
*include/standard-headers/ )
|
||||
# Removing include lines from osdep.h itself would be counterproductive.
|
||||
echo "SKIPPING $f (special case header)"
|
||||
@@ -174,7 +174,7 @@ for f in "$@"; do
|
||||
<limits.h> <unistd.h> <time.h> <ctype.h> <errno.h> <fcntl.h>
|
||||
<sys/stat.h> <sys/time.h> <assert.h> <signal.h> <glib.h>
|
||||
<sys/stat.h> <sys/time.h> <assert.h> <signal.h> <glib.h> <sys/mman.h>
|
||||
"sysemu/os-posix.h, sysemu/os-win32.h "glib-compat.h"
|
||||
"system/os-posix.h, system/os-win32.h "glib-compat.h"
|
||||
"qemu/typedefs.h"
|
||||
))' "$f"
|
||||
|
||||
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#
|
||||
"""Delete stale assets from the download cache of the functional tests"""
|
||||
|
||||
import os
|
||||
import stat
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
cache_dir_env = os.getenv('QEMU_TEST_CACHE_DIR')
|
||||
if cache_dir_env:
|
||||
cache_dir = Path(cache_dir_env, "download")
|
||||
else:
|
||||
cache_dir = Path(Path("~").expanduser(), ".cache", "qemu", "download")
|
||||
|
||||
if not cache_dir.exists():
|
||||
print(f"Cache dir {cache_dir} does not exist!", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
os.chdir(cache_dir)
|
||||
|
||||
for file in cache_dir.iterdir():
|
||||
# Only consider the files that use a sha256 as filename:
|
||||
if len(file.name) != 64:
|
||||
continue
|
||||
|
||||
try:
|
||||
timestamp = int(file.with_suffix(".stamp").read_text())
|
||||
except FileNotFoundError:
|
||||
# Assume it's an old file that was already in the cache before we
|
||||
# added the code for evicting stale assets. Use the release date
|
||||
# of QEMU v10.1 as a default timestamp.
|
||||
timestamp = time.mktime((2025, 8, 26, 0, 0, 0, 0, 0, 0))
|
||||
|
||||
age = time.time() - timestamp
|
||||
|
||||
# Delete files older than half of a year (183 days * 24h * 60m * 60s)
|
||||
if age > 15811200:
|
||||
print(f"Removing {cache_dir}/{file.name}.")
|
||||
file.chmod(stat.S_IWRITE)
|
||||
file.unlink()
|
||||
@@ -23,11 +23,7 @@
|
||||
#define G_GNUC_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
|
||||
#define G_GNUC_NULL_TERMINATED __attribute__((sentinel))
|
||||
|
||||
#if defined(_WIN32) && (defined(__x86_64__) || defined(__i386__))
|
||||
# define QEMU_PACKED __attribute__((gcc_struct, packed))
|
||||
#else
|
||||
# define QEMU_PACKED __attribute__((packed))
|
||||
#endif
|
||||
#define QEMU_PACKED __attribute__((packed))
|
||||
|
||||
#define cat(x,y) x ## y
|
||||
#define cat2(x,y) cat(x,y)
|
||||
|
||||
@@ -21,13 +21,6 @@ expression E1, E2, E3, E4, E5;
|
||||
+ address_space_rw(E1, E2, E3, E4, E5, true)
|
||||
|
|
||||
|
||||
- cpu_physical_memory_rw(E1, E2, E3, 0)
|
||||
+ cpu_physical_memory_rw(E1, E2, E3, false)
|
||||
|
|
||||
- cpu_physical_memory_rw(E1, E2, E3, 1)
|
||||
+ cpu_physical_memory_rw(E1, E2, E3, true)
|
||||
|
|
||||
|
||||
- cpu_physical_memory_map(E1, E2, 0)
|
||||
+ cpu_physical_memory_map(E1, E2, false)
|
||||
|
|
||||
@@ -62,18 +55,6 @@ symbol true, false;
|
||||
+ address_space_write(E1, E2, E3, E4, E5)
|
||||
)
|
||||
|
||||
// Avoid uses of cpu_physical_memory_rw() with a constant is_write argument.
|
||||
@@
|
||||
expression E1, E2, E3;
|
||||
@@
|
||||
(
|
||||
- cpu_physical_memory_rw(E1, E2, E3, false)
|
||||
+ cpu_physical_memory_read(E1, E2, E3)
|
||||
|
|
||||
- cpu_physical_memory_rw(E1, E2, E3, true)
|
||||
+ cpu_physical_memory_write(E1, E2, E3)
|
||||
)
|
||||
|
||||
// Remove useless cast
|
||||
@@
|
||||
expression E1, E2, E3, E4, E5, E6;
|
||||
@@ -93,9 +74,6 @@ type T;
|
||||
+ address_space_write_rom(E1, E2, E3, E4, E5)
|
||||
|
|
||||
|
||||
- cpu_physical_memory_rw(E1, (T *)(E2), E3, E4)
|
||||
+ cpu_physical_memory_rw(E1, E2, E3, E4)
|
||||
|
|
||||
- cpu_physical_memory_read(E1, (T *)(E2), E3)
|
||||
+ cpu_physical_memory_read(E1, E2, E3)
|
||||
|
|
||||
|
||||
@@ -798,7 +798,8 @@ class RedundantTypeSizes(TypeInfoVar):
|
||||
#
|
||||
#
|
||||
# if 'class_init' not in fields:
|
||||
# yield self.prepend(('static void %s_class_init(ObjectClass *oc, void *data)\n'
|
||||
# yield self.prepend(('static void %s_class_init(ObjectClass *oc,\n'
|
||||
# 'const void *data)\n'
|
||||
# '{\n'
|
||||
# '}\n\n') % (ids.lowercase))
|
||||
# yield self.append_field('class_init', ids.lowercase+'_class_init')
|
||||
@@ -901,26 +902,6 @@ class TypeRegisterCall(FileMatch):
|
||||
regexp = S(r'^[ \t]*', NAMED('func_name', 'type_register'),
|
||||
r'\s*\(&\s*', NAMED('name', RE_IDENTIFIER), r'\s*\);[ \t]*\n')
|
||||
|
||||
class MakeTypeRegisterStatic(TypeRegisterCall):
|
||||
"""Make type_register() call static if variable is static const"""
|
||||
def gen_patches(self):
|
||||
var = self.file.find_match(TypeInfoVar, self.name)
|
||||
if var is None:
|
||||
self.warn("can't find TypeInfo var declaration for %s", self.name)
|
||||
return
|
||||
if var.is_static() and var.is_const():
|
||||
yield self.group_match('func_name').make_patch('type_register_static')
|
||||
|
||||
class MakeTypeRegisterNotStatic(TypeRegisterStaticCall):
|
||||
"""Make type_register() call static if variable is static const"""
|
||||
def gen_patches(self):
|
||||
var = self.file.find_match(TypeInfoVar, self.name)
|
||||
if var is None:
|
||||
self.warn("can't find TypeInfo var declaration for %s", self.name)
|
||||
return
|
||||
if not var.is_static() or not var.is_const():
|
||||
yield self.group_match('func_name').make_patch('type_register')
|
||||
|
||||
class TypeInfoMacro(FileMatch):
|
||||
"""TYPE_INFO macro usage"""
|
||||
regexp = S(r'^[ \t]*TYPE_INFO\s*\(\s*', NAMED('name', RE_IDENTIFIER), r'\s*\)[ \t]*;?[ \t]*\n')
|
||||
|
||||
@@ -70,15 +70,15 @@ static const TypeInfo char_file_type_info = {
|
||||
.name = armsse_variants[i].name,
|
||||
.parent = TYPE_ARMSSE,
|
||||
.class_init = armsse_class_init,
|
||||
.class_data = (void *)&armsse_variants[i],
|
||||
.class_data = &armsse_variants[i],
|
||||
};''', re.MULTILINE)
|
||||
|
||||
print(RE_ARRAY_ITEM)
|
||||
assert fullmatch(RE_ARRAY_ITEM, '{ TYPE_HOTPLUG_HANDLER },')
|
||||
assert fullmatch(RE_ARRAY_ITEM, '{ TYPE_ACPI_DEVICE_IF },')
|
||||
assert fullmatch(RE_ARRAY_ITEM, '{ }')
|
||||
assert fullmatch(RE_ARRAY_CAST, '(InterfaceInfo[])')
|
||||
assert fullmatch(RE_ARRAY, '''(InterfaceInfo[]) {
|
||||
assert fullmatch(RE_ARRAY_CAST, '(const InterfaceInfo[])')
|
||||
assert fullmatch(RE_ARRAY, '''(const InterfaceInfo[]) {
|
||||
{ TYPE_HOTPLUG_HANDLER },
|
||||
{ TYPE_ACPI_DEVICE_IF },
|
||||
{ }
|
||||
@@ -98,7 +98,7 @@ static const TypeInfo char_file_type_info = {
|
||||
.parent = TYPE_DEVICE,
|
||||
.instance_size = sizeof(CRBState),
|
||||
.class_init = tpm_crb_class_init,
|
||||
.interfaces = (InterfaceInfo[]) {
|
||||
.interfaces = (const InterfaceInfo[]) {
|
||||
{ TYPE_TPM_IF },
|
||||
{ }
|
||||
}
|
||||
@@ -134,7 +134,7 @@ static const TypeInfo char_file_type_info = {
|
||||
.instance_size = sizeof(AcpiGedState),
|
||||
.instance_init = acpi_ged_initfn,
|
||||
.class_init = acpi_ged_class_init,
|
||||
.interfaces = (InterfaceInfo[]) {
|
||||
.interfaces = (const InterfaceInfo[]) {
|
||||
{ TYPE_HOTPLUG_HANDLER },
|
||||
{ TYPE_ACPI_DEVICE_IF },
|
||||
{ }
|
||||
@@ -164,7 +164,7 @@ static const TypeInfo char_file_type_info = {
|
||||
.parent = TYPE_DEVICE,
|
||||
.instance_size = sizeof(CRBState),
|
||||
.class_init = tpm_crb_class_init,
|
||||
.interfaces = (InterfaceInfo[]) {
|
||||
.interfaces = (const InterfaceInfo[]) {
|
||||
{ TYPE_TPM_IF },
|
||||
{ }
|
||||
}
|
||||
@@ -264,12 +264,12 @@ def test_initial_includes():
|
||||
#define SILENT_ES1370
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "hw/audio/soundhw.h"
|
||||
#include "audio/audio.h"
|
||||
#include "hw/audio/model.h"
|
||||
#include "qemu/audio.h"
|
||||
#include "hw/pci/pci.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "qemu/module.h"
|
||||
#include "sysemu/dma.h"
|
||||
#include "system/dma.h"
|
||||
|
||||
/* Missing stuff:
|
||||
SCTRL_P[12](END|ST)INC
|
||||
@@ -278,5 +278,5 @@ def test_initial_includes():
|
||||
m = InitialIncludes.domatch(c)
|
||||
assert m
|
||||
print(repr(m.group(0)))
|
||||
assert m.group(0).endswith('#include "sysemu/dma.h"\n')
|
||||
assert m.group(0).endswith('#include "system/dma.h"\n')
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ chardev
|
||||
~ .*/qemu((/include)?/chardev/.*)
|
||||
|
||||
crypto
|
||||
~ .*/qemu((/include)?/crypto/.*|/hw/.*/.*crypto.*|(/include/sysemu|/backends)/cryptodev.*|/host/include/.*/host/crypto/.*)
|
||||
~ .*/qemu((/include)?/crypto/.*|/hw/.*/.*crypto.*|(/include/system|/backends)/cryptodev.*|/host/include/.*/host/crypto/.*)
|
||||
|
||||
disas
|
||||
~ .*/qemu((/include)?/disas.*)
|
||||
@@ -144,9 +144,12 @@ kvm
|
||||
tcg
|
||||
~ .*/qemu(/accel/tcg|/replay|/tcg)/.*
|
||||
|
||||
sysemu
|
||||
system
|
||||
~ .*/qemu(/system/.*|/accel/.*)
|
||||
|
||||
plugins
|
||||
~ .*/qemu(/contrib|/tests/tcg)?/plugins/.*
|
||||
|
||||
(headers)
|
||||
~ .*/qemu(/include/.*)
|
||||
|
||||
|
||||
@@ -1016,9 +1016,12 @@ def infer_format(arg, fieldmask, flds, width):
|
||||
else:
|
||||
var_flds[n] = c
|
||||
|
||||
if not arg:
|
||||
arg = infer_argument_set(flds)
|
||||
|
||||
# Look for an existing format with the same argument set and fields
|
||||
for fmt in formats.values():
|
||||
if arg and fmt.base != arg:
|
||||
if fmt.base != arg:
|
||||
continue
|
||||
if fieldmask != fmt.fieldmask:
|
||||
continue
|
||||
@@ -1029,8 +1032,6 @@ def infer_format(arg, fieldmask, flds, width):
|
||||
return (fmt, const_flds)
|
||||
|
||||
name = decode_function + '_Fmt_' + str(len(formats))
|
||||
if not arg:
|
||||
arg = infer_argument_set(flds)
|
||||
|
||||
fmt = Format(name, 0, arg, 0, 0, 0, fieldmask, var_flds, width)
|
||||
formats[name] = fmt
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
# with this program; if not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Run QEMU with all combinations of -machine and -device types,
|
||||
@@ -528,7 +527,7 @@ def main():
|
||||
# Async QMP, when in use, is chatty about connection failures.
|
||||
# This script knowingly generates a ton of connection errors.
|
||||
# Silence this logger.
|
||||
logging.getLogger('qemu.qmp.qmp_client').setLevel(logging.CRITICAL)
|
||||
logging.getLogger('qemu.qmp.protocol').setLevel(logging.CRITICAL)
|
||||
|
||||
fatal_failures = []
|
||||
wl_stats = {}
|
||||
|
||||
Executable
+190
@@ -0,0 +1,190 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
get-wraps-from-cargo-registry.py - Update Meson subprojects from a global registry
|
||||
"""
|
||||
|
||||
# Copyright (C) 2025 Red Hat, Inc.
|
||||
#
|
||||
# Author: Paolo Bonzini <pbonzini@redhat.com>
|
||||
|
||||
import argparse
|
||||
import configparser
|
||||
import filecmp
|
||||
import glob
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def get_name_and_semver(namever: str) -> tuple[str, str]:
|
||||
"""Split a subproject name into its name and semantic version parts"""
|
||||
parts = namever.rsplit("-", 1)
|
||||
if len(parts) != 2:
|
||||
return namever, ""
|
||||
|
||||
return parts[0], parts[1]
|
||||
|
||||
|
||||
class UpdateSubprojects:
|
||||
cargo_registry: str
|
||||
top_srcdir: str
|
||||
dry_run: bool
|
||||
changes: int = 0
|
||||
|
||||
def find_installed_crate(self, namever: str) -> str | None:
|
||||
"""Find installed crate matching name and semver prefix"""
|
||||
name, semver = get_name_and_semver(namever)
|
||||
|
||||
# exact version match
|
||||
path = os.path.join(self.cargo_registry, f"{name}-{semver}")
|
||||
if os.path.exists(path):
|
||||
return f"{name}-{semver}"
|
||||
|
||||
# semver match
|
||||
matches = sorted(glob.glob(f"{path}.*"))
|
||||
return os.path.basename(matches[0]) if matches else None
|
||||
|
||||
def compare_build_rs(self, orig_dir: str, registry_namever: str) -> None:
|
||||
"""Warn if the build.rs in the original directory differs from the registry version."""
|
||||
orig_build_rs = os.path.join(orig_dir, "build.rs")
|
||||
new_build_rs = os.path.join(self.cargo_registry, registry_namever, "build.rs")
|
||||
|
||||
msg = None
|
||||
if os.path.isfile(orig_build_rs) != os.path.isfile(new_build_rs):
|
||||
if os.path.isfile(orig_build_rs):
|
||||
msg = f"build.rs removed in {registry_namever}"
|
||||
if os.path.isfile(new_build_rs):
|
||||
msg = f"build.rs added in {registry_namever}"
|
||||
|
||||
elif os.path.isfile(orig_build_rs) and not filecmp.cmp(orig_build_rs, new_build_rs):
|
||||
msg = f"build.rs changed from {orig_dir} to {registry_namever}"
|
||||
|
||||
if msg:
|
||||
print(f"⚠️ Warning: {msg}")
|
||||
print(" This may affect the build process - please review the differences.")
|
||||
|
||||
def update_subproject(self, wrap_file: str, registry_namever: str) -> None:
|
||||
"""Modify [wrap-file] section to point to self.cargo_registry."""
|
||||
assert wrap_file.endswith("-rs.wrap")
|
||||
wrap_name = wrap_file[:-5]
|
||||
|
||||
env = os.environ.copy()
|
||||
env["MESON_PACKAGE_CACHE_DIR"] = self.cargo_registry
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
config.read(wrap_file)
|
||||
if "wrap-file" not in config:
|
||||
return
|
||||
|
||||
# do not download the wrap, always use the local copy
|
||||
orig_dir = config["wrap-file"]["directory"]
|
||||
if os.path.exists(orig_dir) and orig_dir != registry_namever:
|
||||
self.compare_build_rs(orig_dir, registry_namever)
|
||||
|
||||
if self.dry_run:
|
||||
if orig_dir == registry_namever:
|
||||
print(f"Will install {orig_dir} from registry.")
|
||||
else:
|
||||
print(f"Will replace {orig_dir} with {registry_namever}.")
|
||||
self.changes += 1
|
||||
return
|
||||
|
||||
config["wrap-file"]["directory"] = registry_namever
|
||||
for key in list(config["wrap-file"].keys()):
|
||||
if key.startswith("source"):
|
||||
del config["wrap-file"][key]
|
||||
|
||||
# replace existing directory with installed version
|
||||
if os.path.exists(orig_dir):
|
||||
subprocess.run(
|
||||
["meson", "subprojects", "purge", "--confirm", wrap_name],
|
||||
cwd=self.top_srcdir,
|
||||
env=env,
|
||||
check=True,
|
||||
)
|
||||
|
||||
with open(wrap_file, "w") as f:
|
||||
config.write(f)
|
||||
|
||||
if orig_dir == registry_namever:
|
||||
print(f"Installing {orig_dir} from registry.")
|
||||
else:
|
||||
print(f"Replacing {orig_dir} with {registry_namever}.")
|
||||
patch_dir = config["wrap-file"]["patch_directory"]
|
||||
patch_dir = os.path.join("packagefiles", patch_dir)
|
||||
_, ver = registry_namever.rsplit("-", 1)
|
||||
subprocess.run(
|
||||
["meson", "rewrite", "kwargs", "set", "project", "/", "version", ver],
|
||||
cwd=patch_dir,
|
||||
env=env,
|
||||
check=True,
|
||||
)
|
||||
|
||||
subprocess.run(
|
||||
["meson", "subprojects", "download", wrap_name],
|
||||
cwd=self.top_srcdir,
|
||||
env=env,
|
||||
check=True,
|
||||
)
|
||||
self.changes += 1
|
||||
|
||||
@staticmethod
|
||||
def parse_cmdline() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Replace Meson subprojects with packages in a Cargo registry"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--cargo-registry",
|
||||
default=os.environ.get("CARGO_REGISTRY"),
|
||||
help="Path to Cargo registry (default: CARGO_REGISTRY env var)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Do not actually replace anything",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
if not args.cargo_registry:
|
||||
print("error: CARGO_REGISTRY environment variable not set and --cargo-registry not provided")
|
||||
sys.exit(1)
|
||||
|
||||
return args
|
||||
|
||||
def __init__(self, args: argparse.Namespace):
|
||||
self.cargo_registry = args.cargo_registry
|
||||
self.dry_run = args.dry_run
|
||||
self.top_srcdir = os.getcwd()
|
||||
|
||||
def main(self) -> None:
|
||||
if not os.path.exists("subprojects"):
|
||||
print("'subprojects' directory not found, nothing to do.")
|
||||
return
|
||||
|
||||
os.chdir("subprojects")
|
||||
for wrap_file in sorted(glob.glob("*-rs.wrap")):
|
||||
namever = wrap_file[:-8] # Remove '-rs.wrap'
|
||||
|
||||
registry_namever = self.find_installed_crate(namever)
|
||||
if not registry_namever:
|
||||
print(f"No installed crate found for {wrap_file}")
|
||||
continue
|
||||
|
||||
self.update_subproject(wrap_file, registry_namever)
|
||||
|
||||
if self.changes:
|
||||
if self.dry_run:
|
||||
print("Rerun without --dry-run to apply changes.")
|
||||
else:
|
||||
print(f"✨ {self.changes} subproject(s) updated!")
|
||||
else:
|
||||
print("No changes.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = UpdateSubprojects.parse_cmdline()
|
||||
UpdateSubprojects(args).main()
|
||||
Executable
+51
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#
|
||||
# Copyright (C) 2024-2025 Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
|
||||
|
||||
"""
|
||||
Handle ACPI GHESv2 error injection logic QEMU QMP interface.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
from arm_processor_error import ArmProcessorEinj
|
||||
|
||||
EINJ_DESC = """
|
||||
Handle ACPI GHESv2 error injection logic QEMU QMP interface.
|
||||
|
||||
It allows using UEFI BIOS EINJ features to generate GHES records.
|
||||
|
||||
It helps testing CPER and GHES drivers at the guest OS and how
|
||||
userspace applications at the guest handle them.
|
||||
"""
|
||||
|
||||
def main():
|
||||
"""Main program"""
|
||||
|
||||
# Main parser - handle generic args like QEMU QMP TCP socket options
|
||||
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
usage="%(prog)s [options]",
|
||||
description=EINJ_DESC)
|
||||
|
||||
g_options = parser.add_argument_group("QEMU QMP socket options")
|
||||
g_options.add_argument("-H", "--host", default="localhost", type=str,
|
||||
help="host name")
|
||||
g_options.add_argument("-P", "--port", default=4445, type=int,
|
||||
help="TCP port number")
|
||||
g_options.add_argument('-d', '--debug', action='store_true')
|
||||
|
||||
subparsers = parser.add_subparsers()
|
||||
|
||||
ArmProcessorEinj(subparsers)
|
||||
|
||||
args = parser.parse_args()
|
||||
if "func" in args:
|
||||
args.func(args)
|
||||
else:
|
||||
sys.exit(f"Please specify a valid command for {sys.argv[0]}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
-2442
File diff suppressed because it is too large
Load Diff
Executable
+325
@@ -0,0 +1,325 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.
|
||||
#
|
||||
# pylint: disable=C0103,R0915
|
||||
#
|
||||
# Converted from the kernel-doc script originally written in Perl
|
||||
# under GPLv2, copyrighted since 1998 by the following authors:
|
||||
#
|
||||
# Aditya Srivastava <yashsri421@gmail.com>
|
||||
# Akira Yokosawa <akiyks@gmail.com>
|
||||
# Alexander A. Klimov <grandmaster@al2klimov.de>
|
||||
# Alexander Lobakin <aleksander.lobakin@intel.com>
|
||||
# André Almeida <andrealmeid@igalia.com>
|
||||
# Andy Shevchenko <andriy.shevchenko@linux.intel.com>
|
||||
# Anna-Maria Behnsen <anna-maria@linutronix.de>
|
||||
# Armin Kuster <akuster@mvista.com>
|
||||
# Bart Van Assche <bart.vanassche@sandisk.com>
|
||||
# Ben Hutchings <ben@decadent.org.uk>
|
||||
# Borislav Petkov <bbpetkov@yahoo.de>
|
||||
# Chen-Yu Tsai <wenst@chromium.org>
|
||||
# Coco Li <lixiaoyan@google.com>
|
||||
# Conchúr Navid <conchur@web.de>
|
||||
# Daniel Santos <daniel.santos@pobox.com>
|
||||
# Danilo Cesar Lemes de Paula <danilo.cesar@collabora.co.uk>
|
||||
# Dan Luedtke <mail@danrl.de>
|
||||
# Donald Hunter <donald.hunter@gmail.com>
|
||||
# Gabriel Krisman Bertazi <krisman@collabora.co.uk>
|
||||
# Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
||||
# Harvey Harrison <harvey.harrison@gmail.com>
|
||||
# Horia Geanta <horia.geanta@freescale.com>
|
||||
# Ilya Dryomov <idryomov@gmail.com>
|
||||
# Jakub Kicinski <kuba@kernel.org>
|
||||
# Jani Nikula <jani.nikula@intel.com>
|
||||
# Jason Baron <jbaron@redhat.com>
|
||||
# Jason Gunthorpe <jgg@nvidia.com>
|
||||
# Jérémy Bobbio <lunar@debian.org>
|
||||
# Johannes Berg <johannes.berg@intel.com>
|
||||
# Johannes Weiner <hannes@cmpxchg.org>
|
||||
# Jonathan Cameron <Jonathan.Cameron@huawei.com>
|
||||
# Jonathan Corbet <corbet@lwn.net>
|
||||
# Jonathan Neuschäfer <j.neuschaefer@gmx.net>
|
||||
# Kamil Rytarowski <n54@gmx.com>
|
||||
# Kees Cook <kees@kernel.org>
|
||||
# Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
||||
# Levin, Alexander (Sasha Levin) <alexander.levin@verizon.com>
|
||||
# Linus Torvalds <torvalds@linux-foundation.org>
|
||||
# Lucas De Marchi <lucas.demarchi@profusion.mobi>
|
||||
# Mark Rutland <mark.rutland@arm.com>
|
||||
# Markus Heiser <markus.heiser@darmarit.de>
|
||||
# Martin Waitz <tali@admingilde.org>
|
||||
# Masahiro Yamada <masahiroy@kernel.org>
|
||||
# Matthew Wilcox <willy@infradead.org>
|
||||
# Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
|
||||
# Michal Wajdeczko <michal.wajdeczko@intel.com>
|
||||
# Michael Zucchi
|
||||
# Mike Rapoport <rppt@linux.ibm.com>
|
||||
# Niklas Söderlund <niklas.soderlund@corigine.com>
|
||||
# Nishanth Menon <nm@ti.com>
|
||||
# Paolo Bonzini <pbonzini@redhat.com>
|
||||
# Pavan Kumar Linga <pavan.kumar.linga@intel.com>
|
||||
# Pavel Pisa <pisa@cmp.felk.cvut.cz>
|
||||
# Peter Maydell <peter.maydell@linaro.org>
|
||||
# Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
|
||||
# Randy Dunlap <rdunlap@infradead.org>
|
||||
# Richard Kennedy <richard@rsk.demon.co.uk>
|
||||
# Rich Walker <rw@shadow.org.uk>
|
||||
# Rolf Eike Beer <eike-kernel@sf-tec.de>
|
||||
# Sakari Ailus <sakari.ailus@linux.intel.com>
|
||||
# Silvio Fricke <silvio.fricke@gmail.com>
|
||||
# Simon Huggins
|
||||
# Tim Waugh <twaugh@redhat.com>
|
||||
# Tomasz Warniełło <tomasz.warniello@gmail.com>
|
||||
# Utkarsh Tripathi <utripathi2002@gmail.com>
|
||||
# valdis.kletnieks@vt.edu <valdis.kletnieks@vt.edu>
|
||||
# Vegard Nossum <vegard.nossum@oracle.com>
|
||||
# Will Deacon <will.deacon@arm.com>
|
||||
# Yacine Belkadi <yacine.belkadi.1@gmail.com>
|
||||
# Yujie Liu <yujie.liu@intel.com>
|
||||
|
||||
"""
|
||||
kernel_doc
|
||||
==========
|
||||
|
||||
Print formatted kernel documentation to stdout
|
||||
|
||||
Read C language source or header FILEs, extract embedded
|
||||
documentation comments, and print formatted documentation
|
||||
to standard output.
|
||||
|
||||
The documentation comments are identified by the "/**"
|
||||
opening comment mark.
|
||||
|
||||
See Documentation/doc-guide/kernel-doc.rst for the
|
||||
documentation comment syntax.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Import Python modules
|
||||
|
||||
LIB_DIR = "lib/kdoc"
|
||||
SRC_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
sys.path.insert(0, os.path.join(SRC_DIR, LIB_DIR))
|
||||
|
||||
from kdoc_files import KernelFiles # pylint: disable=C0413
|
||||
from kdoc_output import RestFormat, ManFormat # pylint: disable=C0413
|
||||
|
||||
DESC = """
|
||||
Read C language source or header FILEs, extract embedded documentation comments,
|
||||
and print formatted documentation to standard output.
|
||||
|
||||
The documentation comments are identified by the "/**" opening comment mark.
|
||||
|
||||
See Documentation/doc-guide/kernel-doc.rst for the documentation comment syntax.
|
||||
"""
|
||||
|
||||
EXPORT_FILE_DESC = """
|
||||
Specify an additional FILE in which to look for EXPORT_SYMBOL information.
|
||||
|
||||
May be used multiple times.
|
||||
"""
|
||||
|
||||
EXPORT_DESC = """
|
||||
Only output documentation for the symbols that have been
|
||||
exported using EXPORT_SYMBOL() and related macros in any input
|
||||
FILE or -export-file FILE.
|
||||
"""
|
||||
|
||||
INTERNAL_DESC = """
|
||||
Only output documentation for the symbols that have NOT been
|
||||
exported using EXPORT_SYMBOL() and related macros in any input
|
||||
FILE or -export-file FILE.
|
||||
"""
|
||||
|
||||
FUNCTION_DESC = """
|
||||
Only output documentation for the given function or DOC: section
|
||||
title. All other functions and DOC: sections are ignored.
|
||||
|
||||
May be used multiple times.
|
||||
"""
|
||||
|
||||
NOSYMBOL_DESC = """
|
||||
Exclude the specified symbol from the output documentation.
|
||||
|
||||
May be used multiple times.
|
||||
"""
|
||||
|
||||
FILES_DESC = """
|
||||
Header and C source files to be parsed.
|
||||
"""
|
||||
|
||||
WARN_CONTENTS_BEFORE_SECTIONS_DESC = """
|
||||
Warns if there are contents before sections (deprecated).
|
||||
|
||||
This option is kept just for backward-compatibility, but it does nothing,
|
||||
neither here nor at the original Perl script.
|
||||
"""
|
||||
|
||||
|
||||
class MsgFormatter(logging.Formatter):
|
||||
"""Helper class to format warnings on a similar way to kernel-doc.pl"""
|
||||
|
||||
def format(self, record):
|
||||
record.levelname = record.levelname.capitalize()
|
||||
return logging.Formatter.format(self, record)
|
||||
|
||||
def main():
|
||||
"""Main program"""
|
||||
|
||||
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter,
|
||||
description=DESC)
|
||||
|
||||
# Normal arguments
|
||||
|
||||
parser.add_argument("-v", "-verbose", "--verbose", action="store_true",
|
||||
help="Verbose output, more warnings and other information.")
|
||||
|
||||
parser.add_argument("-d", "-debug", "--debug", action="store_true",
|
||||
help="Enable debug messages")
|
||||
|
||||
parser.add_argument("-M", "-modulename", "--modulename",
|
||||
default="Kernel API",
|
||||
help="Allow setting a module name at the output.")
|
||||
|
||||
parser.add_argument("-l", "-enable-lineno", "--enable_lineno",
|
||||
action="store_true",
|
||||
help="Enable line number output (only in ReST mode)")
|
||||
|
||||
# Arguments to control the warning behavior
|
||||
|
||||
parser.add_argument("-Wreturn", "--wreturn", action="store_true",
|
||||
help="Warns about the lack of a return markup on functions.")
|
||||
|
||||
parser.add_argument("-Wshort-desc", "-Wshort-description", "--wshort-desc",
|
||||
action="store_true",
|
||||
help="Warns if initial short description is missing")
|
||||
|
||||
parser.add_argument("-Wcontents-before-sections",
|
||||
"--wcontents-before-sections", action="store_true",
|
||||
help=WARN_CONTENTS_BEFORE_SECTIONS_DESC)
|
||||
|
||||
parser.add_argument("-Wall", "--wall", action="store_true",
|
||||
help="Enable all types of warnings")
|
||||
|
||||
parser.add_argument("-Werror", "--werror", action="store_true",
|
||||
help="Treat warnings as errors.")
|
||||
|
||||
parser.add_argument("-export-file", "--export-file", action='append',
|
||||
help=EXPORT_FILE_DESC)
|
||||
|
||||
# Output format mutually-exclusive group
|
||||
|
||||
out_group = parser.add_argument_group("Output format selection (mutually exclusive)")
|
||||
|
||||
out_fmt = out_group.add_mutually_exclusive_group()
|
||||
|
||||
out_fmt.add_argument("-m", "-man", "--man", action="store_true",
|
||||
help="Output troff manual page format.")
|
||||
out_fmt.add_argument("-r", "-rst", "--rst", action="store_true",
|
||||
help="Output reStructuredText format (default).")
|
||||
out_fmt.add_argument("-N", "-none", "--none", action="store_true",
|
||||
help="Do not output documentation, only warnings.")
|
||||
|
||||
# Output selection mutually-exclusive group
|
||||
|
||||
sel_group = parser.add_argument_group("Output selection (mutually exclusive)")
|
||||
sel_mut = sel_group.add_mutually_exclusive_group()
|
||||
|
||||
sel_mut.add_argument("-e", "-export", "--export", action='store_true',
|
||||
help=EXPORT_DESC)
|
||||
|
||||
sel_mut.add_argument("-i", "-internal", "--internal", action='store_true',
|
||||
help=INTERNAL_DESC)
|
||||
|
||||
sel_mut.add_argument("-s", "-function", "--symbol", action='append',
|
||||
help=FUNCTION_DESC)
|
||||
|
||||
# Those are valid for all 3 types of filter
|
||||
parser.add_argument("-n", "-nosymbol", "--nosymbol", action='append',
|
||||
help=NOSYMBOL_DESC)
|
||||
|
||||
parser.add_argument("-D", "-no-doc-sections", "--no-doc-sections",
|
||||
action='store_true', help="Don't outputt DOC sections")
|
||||
|
||||
parser.add_argument("files", metavar="FILE",
|
||||
nargs="+", help=FILES_DESC)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.wall:
|
||||
args.wreturn = True
|
||||
args.wshort_desc = True
|
||||
args.wcontents_before_sections = True
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
if not args.debug:
|
||||
logger.setLevel(logging.INFO)
|
||||
else:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
formatter = MsgFormatter('%(levelname)s: %(message)s')
|
||||
|
||||
handler = logging.StreamHandler()
|
||||
handler.setFormatter(formatter)
|
||||
|
||||
logger.addHandler(handler)
|
||||
|
||||
python_ver = sys.version_info[:2]
|
||||
if python_ver < (3,6):
|
||||
logger.warning("Python 3.6 or later is required by kernel-doc")
|
||||
|
||||
# Return 0 here to avoid breaking compilation
|
||||
sys.exit(0)
|
||||
|
||||
if python_ver < (3,7):
|
||||
logger.warning("Python 3.7 or later is required for correct results")
|
||||
|
||||
if args.man:
|
||||
out_style = ManFormat(modulename=args.modulename)
|
||||
elif args.none:
|
||||
out_style = None
|
||||
else:
|
||||
out_style = RestFormat()
|
||||
|
||||
kfiles = KernelFiles(verbose=args.verbose,
|
||||
out_style=out_style, werror=args.werror,
|
||||
wreturn=args.wreturn, wshort_desc=args.wshort_desc,
|
||||
wcontents_before_sections=args.wcontents_before_sections)
|
||||
|
||||
kfiles.parse(args.files, export_file=args.export_file)
|
||||
|
||||
for t in kfiles.msg(enable_lineno=args.enable_lineno, export=args.export,
|
||||
internal=args.internal, symbol=args.symbol,
|
||||
nosymbol=args.nosymbol, export_file=args.export_file,
|
||||
no_doc_sections=args.no_doc_sections):
|
||||
msg = t[1]
|
||||
if msg:
|
||||
print(msg)
|
||||
|
||||
error_count = kfiles.errors
|
||||
if not error_count:
|
||||
sys.exit(0)
|
||||
|
||||
if args.werror:
|
||||
print(f"{error_count} warnings as errors")
|
||||
sys.exit(error_count)
|
||||
|
||||
if args.verbose:
|
||||
print(f"{error_count} errors")
|
||||
|
||||
if args.none:
|
||||
sys.exit(0)
|
||||
|
||||
sys.exit(error_count)
|
||||
|
||||
|
||||
# Call main method
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,291 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.
|
||||
#
|
||||
# pylint: disable=R0903,R0913,R0914,R0917
|
||||
|
||||
"""
|
||||
Parse lernel-doc tags on multiple kernel source files.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
|
||||
from kdoc_parser import KernelDoc
|
||||
from kdoc_output import OutputFormat
|
||||
|
||||
|
||||
class GlobSourceFiles:
|
||||
"""
|
||||
Parse C source code file names and directories via an Interactor.
|
||||
"""
|
||||
|
||||
def __init__(self, srctree=None, valid_extensions=None):
|
||||
"""
|
||||
Initialize valid extensions with a tuple.
|
||||
|
||||
If not defined, assume default C extensions (.c and .h)
|
||||
|
||||
It would be possible to use python's glob function, but it is
|
||||
very slow, and it is not interactive. So, it would wait to read all
|
||||
directories before actually do something.
|
||||
|
||||
So, let's use our own implementation.
|
||||
"""
|
||||
|
||||
if not valid_extensions:
|
||||
self.extensions = (".c", ".h")
|
||||
else:
|
||||
self.extensions = valid_extensions
|
||||
|
||||
self.srctree = srctree
|
||||
|
||||
def _parse_dir(self, dirname):
|
||||
"""Internal function to parse files recursively"""
|
||||
|
||||
with os.scandir(dirname) as obj:
|
||||
for entry in obj:
|
||||
name = os.path.join(dirname, entry.name)
|
||||
|
||||
if entry.is_dir():
|
||||
yield from self._parse_dir(name)
|
||||
|
||||
if not entry.is_file():
|
||||
continue
|
||||
|
||||
basename = os.path.basename(name)
|
||||
|
||||
if not basename.endswith(self.extensions):
|
||||
continue
|
||||
|
||||
yield name
|
||||
|
||||
def parse_files(self, file_list, file_not_found_cb):
|
||||
"""
|
||||
Define an interator to parse all source files from file_list,
|
||||
handling directories if any
|
||||
"""
|
||||
|
||||
if not file_list:
|
||||
return
|
||||
|
||||
for fname in file_list:
|
||||
if self.srctree:
|
||||
f = os.path.join(self.srctree, fname)
|
||||
else:
|
||||
f = fname
|
||||
|
||||
if os.path.isdir(f):
|
||||
yield from self._parse_dir(f)
|
||||
elif os.path.isfile(f):
|
||||
yield f
|
||||
elif file_not_found_cb:
|
||||
file_not_found_cb(fname)
|
||||
|
||||
|
||||
class KernelFiles():
|
||||
"""
|
||||
Parse kernel-doc tags on multiple kernel source files.
|
||||
|
||||
There are two type of parsers defined here:
|
||||
- self.parse_file(): parses both kernel-doc markups and
|
||||
EXPORT_SYMBOL* macros;
|
||||
- self.process_export_file(): parses only EXPORT_SYMBOL* macros.
|
||||
"""
|
||||
|
||||
def warning(self, msg):
|
||||
"""Ancillary routine to output a warning and increment error count"""
|
||||
|
||||
self.config.log.warning(msg)
|
||||
self.errors += 1
|
||||
|
||||
def error(self, msg):
|
||||
"""Ancillary routine to output an error and increment error count"""
|
||||
|
||||
self.config.log.error(msg)
|
||||
self.errors += 1
|
||||
|
||||
def parse_file(self, fname):
|
||||
"""
|
||||
Parse a single Kernel source.
|
||||
"""
|
||||
|
||||
# Prevent parsing the same file twice if results are cached
|
||||
if fname in self.files:
|
||||
return
|
||||
|
||||
doc = KernelDoc(self.config, fname)
|
||||
export_table, entries = doc.parse_kdoc()
|
||||
|
||||
self.export_table[fname] = export_table
|
||||
|
||||
self.files.add(fname)
|
||||
self.export_files.add(fname) # parse_kdoc() already check exports
|
||||
|
||||
self.results[fname] = entries
|
||||
|
||||
def process_export_file(self, fname):
|
||||
"""
|
||||
Parses EXPORT_SYMBOL* macros from a single Kernel source file.
|
||||
"""
|
||||
|
||||
# Prevent parsing the same file twice if results are cached
|
||||
if fname in self.export_files:
|
||||
return
|
||||
|
||||
doc = KernelDoc(self.config, fname)
|
||||
export_table = doc.parse_export()
|
||||
|
||||
if not export_table:
|
||||
self.error(f"Error: Cannot check EXPORT_SYMBOL* on {fname}")
|
||||
export_table = set()
|
||||
|
||||
self.export_table[fname] = export_table
|
||||
self.export_files.add(fname)
|
||||
|
||||
def file_not_found_cb(self, fname):
|
||||
"""
|
||||
Callback to warn if a file was not found.
|
||||
"""
|
||||
|
||||
self.error(f"Cannot find file {fname}")
|
||||
|
||||
def __init__(self, verbose=False, out_style=None,
|
||||
werror=False, wreturn=False, wshort_desc=False,
|
||||
wcontents_before_sections=False,
|
||||
logger=None):
|
||||
"""
|
||||
Initialize startup variables and parse all files
|
||||
"""
|
||||
|
||||
if not verbose:
|
||||
verbose = bool(os.environ.get("KBUILD_VERBOSE", 0))
|
||||
|
||||
if out_style is None:
|
||||
out_style = OutputFormat()
|
||||
|
||||
if not werror:
|
||||
kcflags = os.environ.get("KCFLAGS", None)
|
||||
if kcflags:
|
||||
match = re.search(r"(\s|^)-Werror(\s|$)/", kcflags)
|
||||
if match:
|
||||
werror = True
|
||||
|
||||
# reading this variable is for backwards compat just in case
|
||||
# someone was calling it with the variable from outside the
|
||||
# kernel's build system
|
||||
kdoc_werror = os.environ.get("KDOC_WERROR", None)
|
||||
if kdoc_werror:
|
||||
werror = kdoc_werror
|
||||
|
||||
# Some variables are global to the parser logic as a whole as they are
|
||||
# used to send control configuration to KernelDoc class. As such,
|
||||
# those variables are read-only inside the KernelDoc.
|
||||
self.config = argparse.Namespace
|
||||
|
||||
self.config.verbose = verbose
|
||||
self.config.werror = werror
|
||||
self.config.wreturn = wreturn
|
||||
self.config.wshort_desc = wshort_desc
|
||||
self.config.wcontents_before_sections = wcontents_before_sections
|
||||
|
||||
if not logger:
|
||||
self.config.log = logging.getLogger("kernel-doc")
|
||||
else:
|
||||
self.config.log = logger
|
||||
|
||||
self.config.warning = self.warning
|
||||
|
||||
self.config.src_tree = os.environ.get("SRCTREE", None)
|
||||
|
||||
# Initialize variables that are internal to KernelFiles
|
||||
|
||||
self.out_style = out_style
|
||||
|
||||
self.errors = 0
|
||||
self.results = {}
|
||||
|
||||
self.files = set()
|
||||
self.export_files = set()
|
||||
self.export_table = {}
|
||||
|
||||
def parse(self, file_list, export_file=None):
|
||||
"""
|
||||
Parse all files
|
||||
"""
|
||||
|
||||
glob = GlobSourceFiles(srctree=self.config.src_tree)
|
||||
|
||||
for fname in glob.parse_files(file_list, self.file_not_found_cb):
|
||||
self.parse_file(fname)
|
||||
|
||||
for fname in glob.parse_files(export_file, self.file_not_found_cb):
|
||||
self.process_export_file(fname)
|
||||
|
||||
def out_msg(self, fname, name, arg):
|
||||
"""
|
||||
Return output messages from a file name using the output style
|
||||
filtering.
|
||||
|
||||
If output type was not handled by the syler, return None.
|
||||
"""
|
||||
|
||||
# NOTE: we can add rules here to filter out unwanted parts,
|
||||
# although OutputFormat.msg already does that.
|
||||
|
||||
return self.out_style.msg(fname, name, arg)
|
||||
|
||||
def msg(self, enable_lineno=False, export=False, internal=False,
|
||||
symbol=None, nosymbol=None, no_doc_sections=False,
|
||||
filenames=None, export_file=None):
|
||||
"""
|
||||
Interacts over the kernel-doc results and output messages,
|
||||
returning kernel-doc markups on each interaction
|
||||
"""
|
||||
|
||||
self.out_style.set_config(self.config)
|
||||
|
||||
if not filenames:
|
||||
filenames = sorted(self.results.keys())
|
||||
|
||||
glob = GlobSourceFiles(srctree=self.config.src_tree)
|
||||
|
||||
for fname in filenames:
|
||||
function_table = set()
|
||||
|
||||
if internal or export:
|
||||
if not export_file:
|
||||
export_file = [fname]
|
||||
|
||||
for f in glob.parse_files(export_file, self.file_not_found_cb):
|
||||
function_table |= self.export_table[f]
|
||||
|
||||
if symbol:
|
||||
for s in symbol:
|
||||
function_table.add(s)
|
||||
|
||||
self.out_style.set_filter(export, internal, symbol, nosymbol,
|
||||
function_table, enable_lineno,
|
||||
no_doc_sections)
|
||||
|
||||
msg = ""
|
||||
if fname not in self.results:
|
||||
self.config.log.warning("No kernel-doc for file %s", fname)
|
||||
continue
|
||||
|
||||
for arg in self.results[fname]:
|
||||
m = self.out_msg(fname, arg.name, arg)
|
||||
|
||||
if m is None:
|
||||
ln = arg.get("ln", 0)
|
||||
dtype = arg.get('type', "")
|
||||
|
||||
self.config.log.warning("%s:%d Can't handle %s",
|
||||
fname, ln, dtype)
|
||||
else:
|
||||
msg += m
|
||||
|
||||
if msg:
|
||||
yield fname, msg
|
||||
@@ -0,0 +1,42 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# A class that will, eventually, encapsulate all of the parsed data that we
|
||||
# then pass into the output modules.
|
||||
#
|
||||
|
||||
class KdocItem:
|
||||
def __init__(self, name, type, start_line, **other_stuff):
|
||||
self.name = name
|
||||
self.type = type
|
||||
self.declaration_start_line = start_line
|
||||
self.sections = {}
|
||||
self.sections_start_lines = {}
|
||||
self.parameterlist = []
|
||||
self.parameterdesc_start_lines = []
|
||||
self.parameterdescs = {}
|
||||
self.parametertypes = {}
|
||||
#
|
||||
# Just save everything else into our own dict so that the output
|
||||
# side can grab it directly as before. As we move things into more
|
||||
# structured data, this will, hopefully, fade away.
|
||||
#
|
||||
self.other_stuff = other_stuff
|
||||
|
||||
def get(self, key, default = None):
|
||||
return self.other_stuff.get(key, default)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.get(key)
|
||||
|
||||
#
|
||||
# Tracking of section and parameter information.
|
||||
#
|
||||
def set_sections(self, sections, start_lines):
|
||||
self.sections = sections
|
||||
self.section_start_lines = start_lines
|
||||
|
||||
def set_params(self, names, descs, types, starts):
|
||||
self.parameterlist = names
|
||||
self.parameterdescs = descs
|
||||
self.parametertypes = types
|
||||
self.parameterdesc_start_lines = starts
|
||||
@@ -0,0 +1,749 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.
|
||||
#
|
||||
# pylint: disable=C0301,R0902,R0911,R0912,R0913,R0914,R0915,R0917
|
||||
|
||||
"""
|
||||
Implement output filters to print kernel-doc documentation.
|
||||
|
||||
The implementation uses a virtual base class (OutputFormat) which
|
||||
contains a dispatches to virtual methods, and some code to filter
|
||||
out output messages.
|
||||
|
||||
The actual implementation is done on one separate class per each type
|
||||
of output. Currently, there are output classes for ReST and man/troff.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
from kdoc_parser import KernelDoc, type_param
|
||||
from kdoc_re import KernRe
|
||||
|
||||
|
||||
function_pointer = KernRe(r"([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)", cache=False)
|
||||
|
||||
# match expressions used to find embedded type information
|
||||
type_constant = KernRe(r"\b``([^\`]+)``\b", cache=False)
|
||||
type_constant2 = KernRe(r"\%([-_*\w]+)", cache=False)
|
||||
type_func = KernRe(r"(\w+)\(\)", cache=False)
|
||||
type_param_ref = KernRe(r"([\!~\*]?)\@(\w*((\.\w+)|(->\w+))*(\.\.\.)?)", cache=False)
|
||||
|
||||
# Special RST handling for func ptr params
|
||||
type_fp_param = KernRe(r"\@(\w+)\(\)", cache=False)
|
||||
|
||||
# Special RST handling for structs with func ptr params
|
||||
type_fp_param2 = KernRe(r"\@(\w+->\S+)\(\)", cache=False)
|
||||
|
||||
type_env = KernRe(r"(\$\w+)", cache=False)
|
||||
type_enum = KernRe(r"#(enum\s*([_\w]+))", cache=False)
|
||||
type_struct = KernRe(r"#(struct\s*([_\w]+))", cache=False)
|
||||
type_typedef = KernRe(r"#(([A-Z][_\w]*))", cache=False)
|
||||
type_union = KernRe(r"#(union\s*([_\w]+))", cache=False)
|
||||
type_member = KernRe(r"#([_\w]+)(\.|->)([_\w]+)", cache=False)
|
||||
type_fallback = KernRe(r"((?!))", cache=False) # this never matches
|
||||
type_member_func = type_member + KernRe(r"\(\)", cache=False)
|
||||
|
||||
|
||||
class OutputFormat:
|
||||
"""
|
||||
Base class for OutputFormat. If used as-is, it means that only
|
||||
warnings will be displayed.
|
||||
"""
|
||||
|
||||
# output mode.
|
||||
OUTPUT_ALL = 0 # output all symbols and doc sections
|
||||
OUTPUT_INCLUDE = 1 # output only specified symbols
|
||||
OUTPUT_EXPORTED = 2 # output exported symbols
|
||||
OUTPUT_INTERNAL = 3 # output non-exported symbols
|
||||
|
||||
# Virtual member to be overriden at the inherited classes
|
||||
highlights = []
|
||||
|
||||
def __init__(self):
|
||||
"""Declare internal vars and set mode to OUTPUT_ALL"""
|
||||
|
||||
self.out_mode = self.OUTPUT_ALL
|
||||
self.enable_lineno = None
|
||||
self.nosymbol = {}
|
||||
self.symbol = None
|
||||
self.function_table = None
|
||||
self.config = None
|
||||
self.no_doc_sections = False
|
||||
|
||||
self.data = ""
|
||||
|
||||
def set_config(self, config):
|
||||
"""
|
||||
Setup global config variables used by both parser and output.
|
||||
"""
|
||||
|
||||
self.config = config
|
||||
|
||||
def set_filter(self, export, internal, symbol, nosymbol, function_table,
|
||||
enable_lineno, no_doc_sections):
|
||||
"""
|
||||
Initialize filter variables according with the requested mode.
|
||||
|
||||
Only one choice is valid between export, internal and symbol.
|
||||
|
||||
The nosymbol filter can be used on all modes.
|
||||
"""
|
||||
|
||||
self.enable_lineno = enable_lineno
|
||||
self.no_doc_sections = no_doc_sections
|
||||
self.function_table = function_table
|
||||
|
||||
if symbol:
|
||||
self.out_mode = self.OUTPUT_INCLUDE
|
||||
elif export:
|
||||
self.out_mode = self.OUTPUT_EXPORTED
|
||||
elif internal:
|
||||
self.out_mode = self.OUTPUT_INTERNAL
|
||||
else:
|
||||
self.out_mode = self.OUTPUT_ALL
|
||||
|
||||
if nosymbol:
|
||||
self.nosymbol = set(nosymbol)
|
||||
|
||||
|
||||
def highlight_block(self, block):
|
||||
"""
|
||||
Apply the RST highlights to a sub-block of text.
|
||||
"""
|
||||
|
||||
for r, sub in self.highlights:
|
||||
block = r.sub(sub, block)
|
||||
|
||||
return block
|
||||
|
||||
def out_warnings(self, args):
|
||||
"""
|
||||
Output warnings for identifiers that will be displayed.
|
||||
"""
|
||||
|
||||
for log_msg in args.warnings:
|
||||
self.config.warning(log_msg)
|
||||
|
||||
def check_doc(self, name, args):
|
||||
"""Check if DOC should be output"""
|
||||
|
||||
if self.no_doc_sections:
|
||||
return False
|
||||
|
||||
if name in self.nosymbol:
|
||||
return False
|
||||
|
||||
if self.out_mode == self.OUTPUT_ALL:
|
||||
self.out_warnings(args)
|
||||
return True
|
||||
|
||||
if self.out_mode == self.OUTPUT_INCLUDE:
|
||||
if name in self.function_table:
|
||||
self.out_warnings(args)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def check_declaration(self, dtype, name, args):
|
||||
"""
|
||||
Checks if a declaration should be output or not based on the
|
||||
filtering criteria.
|
||||
"""
|
||||
|
||||
if name in self.nosymbol:
|
||||
return False
|
||||
|
||||
if self.out_mode == self.OUTPUT_ALL:
|
||||
self.out_warnings(args)
|
||||
return True
|
||||
|
||||
if self.out_mode in [self.OUTPUT_INCLUDE, self.OUTPUT_EXPORTED]:
|
||||
if name in self.function_table:
|
||||
return True
|
||||
|
||||
if self.out_mode == self.OUTPUT_INTERNAL:
|
||||
if dtype != "function":
|
||||
self.out_warnings(args)
|
||||
return True
|
||||
|
||||
if name not in self.function_table:
|
||||
self.out_warnings(args)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def msg(self, fname, name, args):
|
||||
"""
|
||||
Handles a single entry from kernel-doc parser
|
||||
"""
|
||||
|
||||
self.data = ""
|
||||
|
||||
dtype = args.type
|
||||
|
||||
if dtype == "doc":
|
||||
self.out_doc(fname, name, args)
|
||||
return self.data
|
||||
|
||||
if not self.check_declaration(dtype, name, args):
|
||||
return self.data
|
||||
|
||||
if dtype == "function":
|
||||
self.out_function(fname, name, args)
|
||||
return self.data
|
||||
|
||||
if dtype == "enum":
|
||||
self.out_enum(fname, name, args)
|
||||
return self.data
|
||||
|
||||
if dtype == "typedef":
|
||||
self.out_typedef(fname, name, args)
|
||||
return self.data
|
||||
|
||||
if dtype in ["struct", "union"]:
|
||||
self.out_struct(fname, name, args)
|
||||
return self.data
|
||||
|
||||
# Warn if some type requires an output logic
|
||||
self.config.log.warning("doesn't now how to output '%s' block",
|
||||
dtype)
|
||||
|
||||
return None
|
||||
|
||||
# Virtual methods to be overridden by inherited classes
|
||||
# At the base class, those do nothing.
|
||||
def out_doc(self, fname, name, args):
|
||||
"""Outputs a DOC block"""
|
||||
|
||||
def out_function(self, fname, name, args):
|
||||
"""Outputs a function"""
|
||||
|
||||
def out_enum(self, fname, name, args):
|
||||
"""Outputs an enum"""
|
||||
|
||||
def out_typedef(self, fname, name, args):
|
||||
"""Outputs a typedef"""
|
||||
|
||||
def out_struct(self, fname, name, args):
|
||||
"""Outputs a struct"""
|
||||
|
||||
|
||||
class RestFormat(OutputFormat):
|
||||
"""Consts and functions used by ReST output"""
|
||||
|
||||
highlights = [
|
||||
(type_constant, r"``\1``"),
|
||||
(type_constant2, r"``\1``"),
|
||||
|
||||
# Note: need to escape () to avoid func matching later
|
||||
(type_member_func, r":c:type:`\1\2\3\\(\\) <\1>`"),
|
||||
(type_member, r":c:type:`\1\2\3 <\1>`"),
|
||||
(type_fp_param, r"**\1\\(\\)**"),
|
||||
(type_fp_param2, r"**\1\\(\\)**"),
|
||||
(type_func, r"\1()"),
|
||||
(type_enum, r":c:type:`\1 <\2>`"),
|
||||
(type_struct, r":c:type:`\1 <\2>`"),
|
||||
(type_typedef, r":c:type:`\1 <\2>`"),
|
||||
(type_union, r":c:type:`\1 <\2>`"),
|
||||
|
||||
# in rst this can refer to any type
|
||||
(type_fallback, r":c:type:`\1`"),
|
||||
(type_param_ref, r"**\1\2**")
|
||||
]
|
||||
blankline = "\n"
|
||||
|
||||
sphinx_literal = KernRe(r'^[^.].*::$', cache=False)
|
||||
sphinx_cblock = KernRe(r'^\.\.\ +code-block::', cache=False)
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Creates class variables.
|
||||
|
||||
Not really mandatory, but it is a good coding style and makes
|
||||
pylint happy.
|
||||
"""
|
||||
|
||||
super().__init__()
|
||||
self.lineprefix = ""
|
||||
|
||||
def print_lineno(self, ln):
|
||||
"""Outputs a line number"""
|
||||
|
||||
if self.enable_lineno and ln is not None:
|
||||
ln += 1
|
||||
self.data += f".. LINENO {ln}\n"
|
||||
|
||||
def output_highlight(self, args):
|
||||
"""
|
||||
Outputs a C symbol that may require being converted to ReST using
|
||||
the self.highlights variable
|
||||
"""
|
||||
|
||||
input_text = args
|
||||
output = ""
|
||||
in_literal = False
|
||||
litprefix = ""
|
||||
block = ""
|
||||
|
||||
for line in input_text.strip("\n").split("\n"):
|
||||
|
||||
# If we're in a literal block, see if we should drop out of it.
|
||||
# Otherwise, pass the line straight through unmunged.
|
||||
if in_literal:
|
||||
if line.strip(): # If the line is not blank
|
||||
# If this is the first non-blank line in a literal block,
|
||||
# figure out the proper indent.
|
||||
if not litprefix:
|
||||
r = KernRe(r'^(\s*)')
|
||||
if r.match(line):
|
||||
litprefix = '^' + r.group(1)
|
||||
else:
|
||||
litprefix = ""
|
||||
|
||||
output += line + "\n"
|
||||
elif not KernRe(litprefix).match(line):
|
||||
in_literal = False
|
||||
else:
|
||||
output += line + "\n"
|
||||
else:
|
||||
output += line + "\n"
|
||||
|
||||
# Not in a literal block (or just dropped out)
|
||||
if not in_literal:
|
||||
block += line + "\n"
|
||||
if self.sphinx_literal.match(line) or self.sphinx_cblock.match(line):
|
||||
in_literal = True
|
||||
litprefix = ""
|
||||
output += self.highlight_block(block)
|
||||
block = ""
|
||||
|
||||
# Handle any remaining block
|
||||
if block:
|
||||
output += self.highlight_block(block)
|
||||
|
||||
# Print the output with the line prefix
|
||||
for line in output.strip("\n").split("\n"):
|
||||
self.data += self.lineprefix + line + "\n"
|
||||
|
||||
def out_section(self, args, out_docblock=False):
|
||||
"""
|
||||
Outputs a block section.
|
||||
|
||||
This could use some work; it's used to output the DOC: sections, and
|
||||
starts by putting out the name of the doc section itself, but that
|
||||
tends to duplicate a header already in the template file.
|
||||
"""
|
||||
for section, text in args.sections.items():
|
||||
# Skip sections that are in the nosymbol_table
|
||||
if section in self.nosymbol:
|
||||
continue
|
||||
|
||||
if out_docblock:
|
||||
if not self.out_mode == self.OUTPUT_INCLUDE:
|
||||
self.data += f".. _{section}:\n\n"
|
||||
self.data += f'{self.lineprefix}**{section}**\n\n'
|
||||
else:
|
||||
self.data += f'{self.lineprefix}**{section}**\n\n'
|
||||
|
||||
self.print_lineno(args.section_start_lines.get(section, 0))
|
||||
self.output_highlight(text)
|
||||
self.data += "\n"
|
||||
self.data += "\n"
|
||||
|
||||
def out_doc(self, fname, name, args):
|
||||
if not self.check_doc(name, args):
|
||||
return
|
||||
self.out_section(args, out_docblock=True)
|
||||
|
||||
def out_function(self, fname, name, args):
|
||||
|
||||
oldprefix = self.lineprefix
|
||||
signature = ""
|
||||
|
||||
func_macro = args.get('func_macro', False)
|
||||
if func_macro:
|
||||
signature = name
|
||||
else:
|
||||
if args.get('functiontype'):
|
||||
signature = args['functiontype'] + " "
|
||||
signature += name + " ("
|
||||
|
||||
ln = args.declaration_start_line
|
||||
count = 0
|
||||
for parameter in args.parameterlist:
|
||||
if count != 0:
|
||||
signature += ", "
|
||||
count += 1
|
||||
dtype = args.parametertypes.get(parameter, "")
|
||||
|
||||
if function_pointer.search(dtype):
|
||||
signature += function_pointer.group(1) + parameter + function_pointer.group(3)
|
||||
else:
|
||||
signature += dtype
|
||||
|
||||
if not func_macro:
|
||||
signature += ")"
|
||||
|
||||
self.print_lineno(ln)
|
||||
if args.get('typedef') or not args.get('functiontype'):
|
||||
self.data += f".. c:macro:: {name}\n\n"
|
||||
|
||||
if args.get('typedef'):
|
||||
self.data += " **Typedef**: "
|
||||
self.lineprefix = ""
|
||||
self.output_highlight(args.get('purpose', ""))
|
||||
self.data += "\n\n**Syntax**\n\n"
|
||||
self.data += f" ``{signature}``\n\n"
|
||||
else:
|
||||
self.data += f"``{signature}``\n\n"
|
||||
else:
|
||||
self.data += f".. c:function:: {signature}\n\n"
|
||||
|
||||
if not args.get('typedef'):
|
||||
self.print_lineno(ln)
|
||||
self.lineprefix = " "
|
||||
self.output_highlight(args.get('purpose', ""))
|
||||
self.data += "\n"
|
||||
|
||||
# Put descriptive text into a container (HTML <div>) to help set
|
||||
# function prototypes apart
|
||||
self.lineprefix = " "
|
||||
|
||||
if args.parameterlist:
|
||||
self.data += ".. container:: kernelindent\n\n"
|
||||
self.data += f"{self.lineprefix}**Parameters**\n\n"
|
||||
|
||||
for parameter in args.parameterlist:
|
||||
parameter_name = KernRe(r'\[.*').sub('', parameter)
|
||||
dtype = args.parametertypes.get(parameter, "")
|
||||
|
||||
if dtype:
|
||||
self.data += f"{self.lineprefix}``{dtype}``\n"
|
||||
else:
|
||||
self.data += f"{self.lineprefix}``{parameter}``\n"
|
||||
|
||||
self.print_lineno(args.parameterdesc_start_lines.get(parameter_name, 0))
|
||||
|
||||
self.lineprefix = " "
|
||||
if parameter_name in args.parameterdescs and \
|
||||
args.parameterdescs[parameter_name] != KernelDoc.undescribed:
|
||||
|
||||
self.output_highlight(args.parameterdescs[parameter_name])
|
||||
self.data += "\n"
|
||||
else:
|
||||
self.data += f"{self.lineprefix}*undescribed*\n\n"
|
||||
self.lineprefix = " "
|
||||
|
||||
self.out_section(args)
|
||||
self.lineprefix = oldprefix
|
||||
|
||||
def out_enum(self, fname, name, args):
|
||||
|
||||
oldprefix = self.lineprefix
|
||||
ln = args.declaration_start_line
|
||||
|
||||
self.data += f"\n\n.. c:enum:: {name}\n\n"
|
||||
|
||||
self.print_lineno(ln)
|
||||
self.lineprefix = " "
|
||||
self.output_highlight(args.get('purpose', ''))
|
||||
self.data += "\n"
|
||||
|
||||
self.data += ".. container:: kernelindent\n\n"
|
||||
outer = self.lineprefix + " "
|
||||
self.lineprefix = outer + " "
|
||||
self.data += f"{outer}**Constants**\n\n"
|
||||
|
||||
for parameter in args.parameterlist:
|
||||
self.data += f"{outer}``{parameter}``\n"
|
||||
|
||||
if args.parameterdescs.get(parameter, '') != KernelDoc.undescribed:
|
||||
self.output_highlight(args.parameterdescs[parameter])
|
||||
else:
|
||||
self.data += f"{self.lineprefix}*undescribed*\n\n"
|
||||
self.data += "\n"
|
||||
|
||||
self.lineprefix = oldprefix
|
||||
self.out_section(args)
|
||||
|
||||
def out_typedef(self, fname, name, args):
|
||||
|
||||
oldprefix = self.lineprefix
|
||||
ln = args.declaration_start_line
|
||||
|
||||
self.data += f"\n\n.. c:type:: {name}\n\n"
|
||||
|
||||
self.print_lineno(ln)
|
||||
self.lineprefix = " "
|
||||
|
||||
self.output_highlight(args.get('purpose', ''))
|
||||
|
||||
self.data += "\n"
|
||||
|
||||
self.lineprefix = oldprefix
|
||||
self.out_section(args)
|
||||
|
||||
def out_struct(self, fname, name, args):
|
||||
|
||||
purpose = args.get('purpose', "")
|
||||
declaration = args.get('definition', "")
|
||||
dtype = args.type
|
||||
ln = args.declaration_start_line
|
||||
|
||||
self.data += f"\n\n.. c:{dtype}:: {name}\n\n"
|
||||
|
||||
self.print_lineno(ln)
|
||||
|
||||
oldprefix = self.lineprefix
|
||||
self.lineprefix += " "
|
||||
|
||||
self.output_highlight(purpose)
|
||||
self.data += "\n"
|
||||
|
||||
self.data += ".. container:: kernelindent\n\n"
|
||||
self.data += f"{self.lineprefix}**Definition**::\n\n"
|
||||
|
||||
self.lineprefix = self.lineprefix + " "
|
||||
|
||||
declaration = declaration.replace("\t", self.lineprefix)
|
||||
|
||||
self.data += f"{self.lineprefix}{dtype} {name}" + ' {' + "\n"
|
||||
self.data += f"{declaration}{self.lineprefix}" + "};\n\n"
|
||||
|
||||
self.lineprefix = " "
|
||||
self.data += f"{self.lineprefix}**Members**\n\n"
|
||||
for parameter in args.parameterlist:
|
||||
if not parameter or parameter.startswith("#"):
|
||||
continue
|
||||
|
||||
parameter_name = parameter.split("[", maxsplit=1)[0]
|
||||
|
||||
if args.parameterdescs.get(parameter_name) == KernelDoc.undescribed:
|
||||
continue
|
||||
|
||||
self.print_lineno(args.parameterdesc_start_lines.get(parameter_name, 0))
|
||||
|
||||
self.data += f"{self.lineprefix}``{parameter}``\n"
|
||||
|
||||
self.lineprefix = " "
|
||||
self.output_highlight(args.parameterdescs[parameter_name])
|
||||
self.lineprefix = " "
|
||||
|
||||
self.data += "\n"
|
||||
|
||||
self.data += "\n"
|
||||
|
||||
self.lineprefix = oldprefix
|
||||
self.out_section(args)
|
||||
|
||||
|
||||
class ManFormat(OutputFormat):
|
||||
"""Consts and functions used by man pages output"""
|
||||
|
||||
highlights = (
|
||||
(type_constant, r"\1"),
|
||||
(type_constant2, r"\1"),
|
||||
(type_func, r"\\fB\1\\fP"),
|
||||
(type_enum, r"\\fI\1\\fP"),
|
||||
(type_struct, r"\\fI\1\\fP"),
|
||||
(type_typedef, r"\\fI\1\\fP"),
|
||||
(type_union, r"\\fI\1\\fP"),
|
||||
(type_param, r"\\fI\1\\fP"),
|
||||
(type_param_ref, r"\\fI\1\2\\fP"),
|
||||
(type_member, r"\\fI\1\2\3\\fP"),
|
||||
(type_fallback, r"\\fI\1\\fP")
|
||||
)
|
||||
blankline = ""
|
||||
|
||||
date_formats = [
|
||||
"%a %b %d %H:%M:%S %Z %Y",
|
||||
"%a %b %d %H:%M:%S %Y",
|
||||
"%Y-%m-%d",
|
||||
"%b %d %Y",
|
||||
"%B %d %Y",
|
||||
"%m %d %Y",
|
||||
]
|
||||
|
||||
def __init__(self, modulename):
|
||||
"""
|
||||
Creates class variables.
|
||||
|
||||
Not really mandatory, but it is a good coding style and makes
|
||||
pylint happy.
|
||||
"""
|
||||
|
||||
super().__init__()
|
||||
self.modulename = modulename
|
||||
|
||||
dt = None
|
||||
tstamp = os.environ.get("KBUILD_BUILD_TIMESTAMP")
|
||||
if tstamp:
|
||||
for fmt in self.date_formats:
|
||||
try:
|
||||
dt = datetime.strptime(tstamp, fmt)
|
||||
break
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if not dt:
|
||||
dt = datetime.now()
|
||||
|
||||
self.man_date = dt.strftime("%B %Y")
|
||||
|
||||
def output_highlight(self, block):
|
||||
"""
|
||||
Outputs a C symbol that may require being highlighted with
|
||||
self.highlights variable using troff syntax
|
||||
"""
|
||||
|
||||
contents = self.highlight_block(block)
|
||||
|
||||
if isinstance(contents, list):
|
||||
contents = "\n".join(contents)
|
||||
|
||||
for line in contents.strip("\n").split("\n"):
|
||||
line = KernRe(r"^\s*").sub("", line)
|
||||
if not line:
|
||||
continue
|
||||
|
||||
if line[0] == ".":
|
||||
self.data += "\\&" + line + "\n"
|
||||
else:
|
||||
self.data += line + "\n"
|
||||
|
||||
def out_doc(self, fname, name, args):
|
||||
if not self.check_doc(name, args):
|
||||
return
|
||||
|
||||
self.data += f'.TH "{self.modulename}" 9 "{self.modulename}" "{self.man_date}" "API Manual" LINUX' + "\n"
|
||||
|
||||
for section, text in args.sections.items():
|
||||
self.data += f'.SH "{section}"' + "\n"
|
||||
self.output_highlight(text)
|
||||
|
||||
def out_function(self, fname, name, args):
|
||||
"""output function in man"""
|
||||
|
||||
self.data += f'.TH "{name}" 9 "{name}" "{self.man_date}" "Kernel Hacker\'s Manual" LINUX' + "\n"
|
||||
|
||||
self.data += ".SH NAME\n"
|
||||
self.data += f"{name} \\- {args['purpose']}\n"
|
||||
|
||||
self.data += ".SH SYNOPSIS\n"
|
||||
if args.get('functiontype', ''):
|
||||
self.data += f'.B "{args["functiontype"]}" {name}' + "\n"
|
||||
else:
|
||||
self.data += f'.B "{name}' + "\n"
|
||||
|
||||
count = 0
|
||||
parenth = "("
|
||||
post = ","
|
||||
|
||||
for parameter in args.parameterlist:
|
||||
if count == len(args.parameterlist) - 1:
|
||||
post = ");"
|
||||
|
||||
dtype = args.parametertypes.get(parameter, "")
|
||||
if function_pointer.match(dtype):
|
||||
# Pointer-to-function
|
||||
self.data += f'".BI "{parenth}{function_pointer.group(1)}" " ") ({function_pointer.group(2)}){post}"' + "\n"
|
||||
else:
|
||||
dtype = KernRe(r'([^\*])$').sub(r'\1 ', dtype)
|
||||
|
||||
self.data += f'.BI "{parenth}{dtype}" "{post}"' + "\n"
|
||||
count += 1
|
||||
parenth = ""
|
||||
|
||||
if args.parameterlist:
|
||||
self.data += ".SH ARGUMENTS\n"
|
||||
|
||||
for parameter in args.parameterlist:
|
||||
parameter_name = re.sub(r'\[.*', '', parameter)
|
||||
|
||||
self.data += f'.IP "{parameter}" 12' + "\n"
|
||||
self.output_highlight(args.parameterdescs.get(parameter_name, ""))
|
||||
|
||||
for section, text in args.sections.items():
|
||||
self.data += f'.SH "{section.upper()}"' + "\n"
|
||||
self.output_highlight(text)
|
||||
|
||||
def out_enum(self, fname, name, args):
|
||||
self.data += f'.TH "{self.modulename}" 9 "enum {name}" "{self.man_date}" "API Manual" LINUX' + "\n"
|
||||
|
||||
self.data += ".SH NAME\n"
|
||||
self.data += f"enum {name} \\- {args['purpose']}\n"
|
||||
|
||||
self.data += ".SH SYNOPSIS\n"
|
||||
self.data += f"enum {name}" + " {\n"
|
||||
|
||||
count = 0
|
||||
for parameter in args.parameterlist:
|
||||
self.data += f'.br\n.BI " {parameter}"' + "\n"
|
||||
if count == len(args.parameterlist) - 1:
|
||||
self.data += "\n};\n"
|
||||
else:
|
||||
self.data += ", \n.br\n"
|
||||
|
||||
count += 1
|
||||
|
||||
self.data += ".SH Constants\n"
|
||||
|
||||
for parameter in args.parameterlist:
|
||||
parameter_name = KernRe(r'\[.*').sub('', parameter)
|
||||
self.data += f'.IP "{parameter}" 12' + "\n"
|
||||
self.output_highlight(args.parameterdescs.get(parameter_name, ""))
|
||||
|
||||
for section, text in args.sections.items():
|
||||
self.data += f'.SH "{section}"' + "\n"
|
||||
self.output_highlight(text)
|
||||
|
||||
def out_typedef(self, fname, name, args):
|
||||
module = self.modulename
|
||||
purpose = args.get('purpose')
|
||||
|
||||
self.data += f'.TH "{module}" 9 "{name}" "{self.man_date}" "API Manual" LINUX' + "\n"
|
||||
|
||||
self.data += ".SH NAME\n"
|
||||
self.data += f"typedef {name} \\- {purpose}\n"
|
||||
|
||||
for section, text in args.sections.items():
|
||||
self.data += f'.SH "{section}"' + "\n"
|
||||
self.output_highlight(text)
|
||||
|
||||
def out_struct(self, fname, name, args):
|
||||
module = self.modulename
|
||||
purpose = args.get('purpose')
|
||||
definition = args.get('definition')
|
||||
|
||||
self.data += f'.TH "{module}" 9 "{args.type} {name}" "{self.man_date}" "API Manual" LINUX' + "\n"
|
||||
|
||||
self.data += ".SH NAME\n"
|
||||
self.data += f"{args.type} {name} \\- {purpose}\n"
|
||||
|
||||
# Replace tabs with two spaces and handle newlines
|
||||
declaration = definition.replace("\t", " ")
|
||||
declaration = KernRe(r"\n").sub('"\n.br\n.BI "', declaration)
|
||||
|
||||
self.data += ".SH SYNOPSIS\n"
|
||||
self.data += f"{args.type} {name} " + "{" + "\n.br\n"
|
||||
self.data += f'.BI "{declaration}\n' + "};\n.br\n\n"
|
||||
|
||||
self.data += ".SH Members\n"
|
||||
for parameter in args.parameterlist:
|
||||
if parameter.startswith("#"):
|
||||
continue
|
||||
|
||||
parameter_name = re.sub(r"\[.*", "", parameter)
|
||||
|
||||
if args.parameterdescs.get(parameter_name) == KernelDoc.undescribed:
|
||||
continue
|
||||
|
||||
self.data += f'.IP "{parameter}" 12' + "\n"
|
||||
self.output_highlight(args.parameterdescs.get(parameter_name))
|
||||
|
||||
for section, text in args.sections.items():
|
||||
self.data += f'.SH "{section}"' + "\n"
|
||||
self.output_highlight(text)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,270 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.
|
||||
|
||||
"""
|
||||
Regular expression ancillary classes.
|
||||
|
||||
Those help caching regular expressions and do matching for kernel-doc.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
# Local cache for regular expressions
|
||||
re_cache = {}
|
||||
|
||||
|
||||
class KernRe:
|
||||
"""
|
||||
Helper class to simplify regex declaration and usage,
|
||||
|
||||
It calls re.compile for a given pattern. It also allows adding
|
||||
regular expressions and define sub at class init time.
|
||||
|
||||
Regular expressions can be cached via an argument, helping to speedup
|
||||
searches.
|
||||
"""
|
||||
|
||||
def _add_regex(self, string, flags):
|
||||
"""
|
||||
Adds a new regex or re-use it from the cache.
|
||||
"""
|
||||
self.regex = re_cache.get(string, None)
|
||||
if not self.regex:
|
||||
self.regex = re.compile(string, flags=flags)
|
||||
if self.cache:
|
||||
re_cache[string] = self.regex
|
||||
|
||||
def __init__(self, string, cache=True, flags=0):
|
||||
"""
|
||||
Compile a regular expression and initialize internal vars.
|
||||
"""
|
||||
|
||||
self.cache = cache
|
||||
self.last_match = None
|
||||
|
||||
self._add_regex(string, flags)
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
Return the regular expression pattern.
|
||||
"""
|
||||
return self.regex.pattern
|
||||
|
||||
def __add__(self, other):
|
||||
"""
|
||||
Allows adding two regular expressions into one.
|
||||
"""
|
||||
|
||||
return KernRe(str(self) + str(other), cache=self.cache or other.cache,
|
||||
flags=self.regex.flags | other.regex.flags)
|
||||
|
||||
def match(self, string):
|
||||
"""
|
||||
Handles a re.match storing its results
|
||||
"""
|
||||
|
||||
self.last_match = self.regex.match(string)
|
||||
return self.last_match
|
||||
|
||||
def search(self, string):
|
||||
"""
|
||||
Handles a re.search storing its results
|
||||
"""
|
||||
|
||||
self.last_match = self.regex.search(string)
|
||||
return self.last_match
|
||||
|
||||
def findall(self, string):
|
||||
"""
|
||||
Alias to re.findall
|
||||
"""
|
||||
|
||||
return self.regex.findall(string)
|
||||
|
||||
def split(self, string):
|
||||
"""
|
||||
Alias to re.split
|
||||
"""
|
||||
|
||||
return self.regex.split(string)
|
||||
|
||||
def sub(self, sub, string, count=0):
|
||||
"""
|
||||
Alias to re.sub
|
||||
"""
|
||||
|
||||
return self.regex.sub(sub, string, count=count)
|
||||
|
||||
def group(self, num):
|
||||
"""
|
||||
Returns the group results of the last match
|
||||
"""
|
||||
|
||||
return self.last_match.group(num)
|
||||
|
||||
|
||||
class NestedMatch:
|
||||
"""
|
||||
Finding nested delimiters is hard with regular expressions. It is
|
||||
even harder on Python with its normal re module, as there are several
|
||||
advanced regular expressions that are missing.
|
||||
|
||||
This is the case of this pattern:
|
||||
|
||||
'\\bSTRUCT_GROUP(\\(((?:(?>[^)(]+)|(?1))*)\\))[^;]*;'
|
||||
|
||||
which is used to properly match open/close parenthesis of the
|
||||
string search STRUCT_GROUP(),
|
||||
|
||||
Add a class that counts pairs of delimiters, using it to match and
|
||||
replace nested expressions.
|
||||
|
||||
The original approach was suggested by:
|
||||
https://stackoverflow.com/questions/5454322/python-how-to-match-nested-parentheses-with-regex
|
||||
|
||||
Although I re-implemented it to make it more generic and match 3 types
|
||||
of delimiters. The logic checks if delimiters are paired. If not, it
|
||||
will ignore the search string.
|
||||
"""
|
||||
|
||||
# TODO: make NestedMatch handle multiple match groups
|
||||
#
|
||||
# Right now, regular expressions to match it are defined only up to
|
||||
# the start delimiter, e.g.:
|
||||
#
|
||||
# \bSTRUCT_GROUP\(
|
||||
#
|
||||
# is similar to: STRUCT_GROUP\((.*)\)
|
||||
# except that the content inside the match group is delimiter's aligned.
|
||||
#
|
||||
# The content inside parenthesis are converted into a single replace
|
||||
# group (e.g. r`\1').
|
||||
#
|
||||
# It would be nice to change such definition to support multiple
|
||||
# match groups, allowing a regex equivalent to.
|
||||
#
|
||||
# FOO\((.*), (.*), (.*)\)
|
||||
#
|
||||
# it is probably easier to define it not as a regular expression, but
|
||||
# with some lexical definition like:
|
||||
#
|
||||
# FOO(arg1, arg2, arg3)
|
||||
|
||||
DELIMITER_PAIRS = {
|
||||
'{': '}',
|
||||
'(': ')',
|
||||
'[': ']',
|
||||
}
|
||||
|
||||
RE_DELIM = re.compile(r'[\{\}\[\]\(\)]')
|
||||
|
||||
def _search(self, regex, line):
|
||||
"""
|
||||
Finds paired blocks for a regex that ends with a delimiter.
|
||||
|
||||
The suggestion of using finditer to match pairs came from:
|
||||
https://stackoverflow.com/questions/5454322/python-how-to-match-nested-parentheses-with-regex
|
||||
but I ended using a different implementation to align all three types
|
||||
of delimiters and seek for an initial regular expression.
|
||||
|
||||
The algorithm seeks for open/close paired delimiters and place them
|
||||
into a stack, yielding a start/stop position of each match when the
|
||||
stack is zeroed.
|
||||
|
||||
The algorithm shoud work fine for properly paired lines, but will
|
||||
silently ignore end delimiters that preceeds an start delimiter.
|
||||
This should be OK for kernel-doc parser, as unaligned delimiters
|
||||
would cause compilation errors. So, we don't need to rise exceptions
|
||||
to cover such issues.
|
||||
"""
|
||||
|
||||
stack = []
|
||||
|
||||
for match_re in regex.finditer(line):
|
||||
start = match_re.start()
|
||||
offset = match_re.end()
|
||||
|
||||
d = line[offset - 1]
|
||||
if d not in self.DELIMITER_PAIRS:
|
||||
continue
|
||||
|
||||
end = self.DELIMITER_PAIRS[d]
|
||||
stack.append(end)
|
||||
|
||||
for match in self.RE_DELIM.finditer(line[offset:]):
|
||||
pos = match.start() + offset
|
||||
|
||||
d = line[pos]
|
||||
|
||||
if d in self.DELIMITER_PAIRS:
|
||||
end = self.DELIMITER_PAIRS[d]
|
||||
|
||||
stack.append(end)
|
||||
continue
|
||||
|
||||
# Does the end delimiter match what it is expected?
|
||||
if stack and d == stack[-1]:
|
||||
stack.pop()
|
||||
|
||||
if not stack:
|
||||
yield start, offset, pos + 1
|
||||
break
|
||||
|
||||
def search(self, regex, line):
|
||||
"""
|
||||
This is similar to re.search:
|
||||
|
||||
It matches a regex that it is followed by a delimiter,
|
||||
returning occurrences only if all delimiters are paired.
|
||||
"""
|
||||
|
||||
for t in self._search(regex, line):
|
||||
|
||||
yield line[t[0]:t[2]]
|
||||
|
||||
def sub(self, regex, sub, line, count=0):
|
||||
"""
|
||||
This is similar to re.sub:
|
||||
|
||||
It matches a regex that it is followed by a delimiter,
|
||||
replacing occurrences only if all delimiters are paired.
|
||||
|
||||
if r'\1' is used, it works just like re: it places there the
|
||||
matched paired data with the delimiter stripped.
|
||||
|
||||
If count is different than zero, it will replace at most count
|
||||
items.
|
||||
"""
|
||||
out = ""
|
||||
|
||||
cur_pos = 0
|
||||
n = 0
|
||||
|
||||
for start, end, pos in self._search(regex, line):
|
||||
out += line[cur_pos:start]
|
||||
|
||||
# Value, ignoring start/end delimiters
|
||||
value = line[end:pos - 1]
|
||||
|
||||
# replaces \1 at the sub string, if \1 is used there
|
||||
new_sub = sub
|
||||
new_sub = new_sub.replace(r'\1', value)
|
||||
|
||||
out += new_sub
|
||||
|
||||
# Drop end ';' if any
|
||||
if line[pos] == ';':
|
||||
pos += 1
|
||||
|
||||
cur_pos = pos
|
||||
n += 1
|
||||
|
||||
if count and count >= n:
|
||||
break
|
||||
|
||||
# Append the remaining string
|
||||
l = len(line)
|
||||
out += line[cur_pos:l]
|
||||
|
||||
return out
|
||||
+17
-17
@@ -11,8 +11,9 @@
|
||||
# See the COPYING file in the top-level directory.
|
||||
|
||||
function subproject_dir() {
|
||||
if test ! -f "subprojects/$1.wrap"; then
|
||||
error "scripts/archive-source.sh should only process wrap subprojects"
|
||||
if test ! -f "$src/subprojects/$1.wrap"; then
|
||||
echo "scripts/archive-source.sh should only process wrap subprojects"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Print the directory key of the wrap file, defaulting to the
|
||||
@@ -26,7 +27,7 @@ function subproject_dir() {
|
||||
-e 's///p' \
|
||||
-e 'q' \
|
||||
-e '}' \
|
||||
"subprojects/$1.wrap")
|
||||
"$src/subprojects/$1.wrap")
|
||||
|
||||
echo "${dir:-$1}"
|
||||
}
|
||||
@@ -39,8 +40,9 @@ fi
|
||||
|
||||
# Only include wraps that are invoked with subproject()
|
||||
SUBPROJECTS="libvfio-user keycodemapdb berkeley-softfloat-3
|
||||
berkeley-testfloat-3 arbitrary-int-1-rs bilge-0.2-rs
|
||||
bilge-impl-0.2-rs either-1-rs itertools-0.11-rs proc-macro2-1-rs
|
||||
berkeley-testfloat-3 anyhow-1-rs arbitrary-int-1-rs attrs-0.2-rs bilge-0.2-rs
|
||||
bilge-impl-0.2-rs either-1-rs foreign-0.3-rs itertools-0.11-rs
|
||||
libc-0.2-rs proc-macro2-1-rs
|
||||
proc-macro-error-1-rs proc-macro-error-attr-1-rs quote-1-rs
|
||||
syn-2-rs unicode-ident-1-rs"
|
||||
|
||||
@@ -60,23 +62,21 @@ meson subprojects download $SUBPROJECTS
|
||||
(cd roms/skiboot && ./make_version.sh > .version)
|
||||
# Fetch edk2 submodule's submodules, since it won't have access to them via
|
||||
# the tarball later.
|
||||
#
|
||||
# A more uniform way to handle this sort of situation would be nice, but we
|
||||
# don't necessarily have much control over how a submodule handles its
|
||||
# submodule dependencies, so we continue to handle these on a case-by-case
|
||||
# basis for now.
|
||||
(cd roms/edk2 && \
|
||||
git submodule update --init --depth 1 -- \
|
||||
ArmPkg/Library/ArmSoftFloatLib/berkeley-softfloat-3 \
|
||||
BaseTools/Source/C/BrotliCompress/brotli \
|
||||
CryptoPkg/Library/OpensslLib/openssl \
|
||||
MdeModulePkg/Library/BrotliCustomDecompressLib/brotli)
|
||||
|
||||
# As recommended by the EDK2 readme, we don't use --recursive here.
|
||||
# EDK2 won't use any code or feature from a submodule of a submodule,
|
||||
# so we don't need to add them to the tarball.
|
||||
# Although we don't necessarily need all of the submodules that EDK2
|
||||
# has, we clone them all, to avoid running into problems where EDK2
|
||||
# adds a new submodule or changes its use of an existing one and
|
||||
# the sources we ship in the tarball then fail to build.
|
||||
(cd roms/edk2 && git submodule update --init --depth 1)
|
||||
popd
|
||||
|
||||
exclude=(--exclude=.git)
|
||||
# include the tarballs in subprojects/packagecache but not their expansion
|
||||
for sp in $SUBPROJECTS; do
|
||||
if grep -xqF "[wrap-file]" subprojects/$sp.wrap; then
|
||||
if grep -xqF "[wrap-file]" $src/subprojects/$sp.wrap; then
|
||||
exclude+=(--exclude=subprojects/"$(subproject_dir $sp)")
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -111,13 +111,13 @@ def help_line(left, opt, indent, long):
|
||||
right = f'{opt["description"]}'
|
||||
if long:
|
||||
value = get_help(opt)
|
||||
if value != "auto" and value != "":
|
||||
if value not in {"", "auto"}:
|
||||
right += f" [{value}]"
|
||||
if "choices" in opt and long:
|
||||
choices = "/".join(sorted(opt["choices"]))
|
||||
right += f" (choices: {choices})"
|
||||
for x in wrap(" " + left, right, indent):
|
||||
sh_print(x)
|
||||
for line in wrap(" " + left, right, indent):
|
||||
sh_print(line)
|
||||
|
||||
|
||||
# Return whether the option (a dictionary) can be used with
|
||||
@@ -144,18 +144,18 @@ def require_arg(opt):
|
||||
return not ({"enabled", "disabled"}.intersection(opt["choices"]))
|
||||
|
||||
|
||||
def filter_options(json):
|
||||
if ":" in json["name"]:
|
||||
def filter_options(opt):
|
||||
if ":" in opt["name"]:
|
||||
return False
|
||||
if json["section"] == "user":
|
||||
return json["name"] not in SKIP_OPTIONS
|
||||
if opt["section"] == "user":
|
||||
return opt["name"] not in SKIP_OPTIONS
|
||||
else:
|
||||
return json["name"] in BUILTIN_OPTIONS
|
||||
return opt["name"] in BUILTIN_OPTIONS
|
||||
|
||||
|
||||
def load_options(json):
|
||||
json = [x for x in json if filter_options(x)]
|
||||
return sorted(json, key=lambda x: x["name"])
|
||||
def load_options(opts):
|
||||
opts = [opt for opt in opts if filter_options(opt)]
|
||||
return sorted(opts, key=lambda opt: opt["name"])
|
||||
|
||||
|
||||
def cli_option(opt):
|
||||
@@ -223,7 +223,7 @@ def print_parse(options):
|
||||
key = cli_option(opt)
|
||||
name = opt["name"]
|
||||
if require_arg(opt):
|
||||
if opt["type"] == "array" and not "choices" in opt:
|
||||
if opt["type"] == "array" and "choices" not in opt:
|
||||
print(f' --{key}=*) quote_sh "-D{name}=$(meson_option_build_array $2)" ;;')
|
||||
else:
|
||||
print(f' --{key}=*) quote_sh "-D{name}=$2" ;;')
|
||||
@@ -242,7 +242,18 @@ def print_parse(options):
|
||||
print("}")
|
||||
|
||||
|
||||
options = load_options(json.load(sys.stdin))
|
||||
print("# This file is generated by meson-buildoptions.py, do not edit!")
|
||||
print_help(options)
|
||||
print_parse(options)
|
||||
def main():
|
||||
json_data = sys.stdin.read()
|
||||
try:
|
||||
options = load_options(json.loads(json_data))
|
||||
except:
|
||||
print("Failure in scripts/meson-buildoptions.py parsing stdin as json",
|
||||
file=sys.stderr)
|
||||
print(json_data, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
print("# This file is generated by meson-buildoptions.py, do not edit!")
|
||||
print_help(options)
|
||||
print_parse(options)
|
||||
|
||||
|
||||
sys.exit(main())
|
||||
|
||||
@@ -58,6 +58,7 @@ meson_options_help() {
|
||||
printf "%s\n" ' --enable-ubsan enable undefined behaviour sanitizer'
|
||||
printf "%s\n" ' --firmwarepath=VALUES search PATH for firmware files [share/qemu-'
|
||||
printf "%s\n" ' firmware]'
|
||||
printf "%s\n" ' --gdb=VALUE Path to GDB'
|
||||
printf "%s\n" ' --iasl=VALUE Path to ACPI disassembler'
|
||||
printf "%s\n" ' --includedir=VALUE Header file directory [include]'
|
||||
printf "%s\n" ' --interp-prefix=VALUE where to find shared libraries etc., use %M for'
|
||||
@@ -81,7 +82,7 @@ meson_options_help() {
|
||||
printf "%s\n" ' [NORMAL]'
|
||||
printf "%s\n" ' --vtune=VALUE Path to VTune directory for profiling'
|
||||
printf "%s\n" ' --with-coroutine=CHOICE coroutine backend to use (choices:'
|
||||
printf "%s\n" ' auto/sigaltstack/ucontext/windows)'
|
||||
printf "%s\n" ' auto/sigaltstack/ucontext/wasm/windows)'
|
||||
printf "%s\n" ' --with-pkgversion=VALUE use specified string as sub-version of the'
|
||||
printf "%s\n" ' package'
|
||||
printf "%s\n" ' --with-suffix=VALUE Suffix for QEMU data/modules/config directories'
|
||||
@@ -98,8 +99,6 @@ meson_options_help() {
|
||||
printf "%s\n" ' alsa ALSA sound support'
|
||||
printf "%s\n" ' attr attr/xattr support'
|
||||
printf "%s\n" ' auth-pam PAM access control'
|
||||
printf "%s\n" ' avx2 AVX2 optimizations'
|
||||
printf "%s\n" ' avx512bw AVX512BW optimizations'
|
||||
printf "%s\n" ' blkio libblkio block device driver'
|
||||
printf "%s\n" ' bochs bochs image format support'
|
||||
printf "%s\n" ' bpf eBPF support'
|
||||
@@ -132,6 +131,7 @@ meson_options_help() {
|
||||
printf "%s\n" ' hv-balloon hv-balloon driver (requires Glib 2.68+ GTree API)'
|
||||
printf "%s\n" ' hvf HVF acceleration support'
|
||||
printf "%s\n" ' iconv Font glyph conversion support'
|
||||
printf "%s\n" ' igvm Independent Guest Virtual Machine (IGVM) file support'
|
||||
printf "%s\n" ' jack JACK sound support'
|
||||
printf "%s\n" ' keyring Linux keyring support'
|
||||
printf "%s\n" ' kvm KVM acceleration support'
|
||||
@@ -155,6 +155,7 @@ meson_options_help() {
|
||||
printf "%s\n" ' membarrier membarrier system call (for Linux 4.14+ or Windows'
|
||||
printf "%s\n" ' modules modules support (non Windows)'
|
||||
printf "%s\n" ' mpath Multipath persistent reservation passthrough'
|
||||
printf "%s\n" ' mshv MSHV acceleration support'
|
||||
printf "%s\n" ' multiprocess Out of process device emulation support'
|
||||
printf "%s\n" ' netmap netmap network backend support'
|
||||
printf "%s\n" ' nettle nettle cryptography support'
|
||||
@@ -164,10 +165,12 @@ meson_options_help() {
|
||||
printf "%s\n" ' oss OSS sound support'
|
||||
printf "%s\n" ' pa PulseAudio sound support'
|
||||
printf "%s\n" ' parallels parallels image format support'
|
||||
printf "%s\n" ' passt passt network backend support'
|
||||
printf "%s\n" ' pipewire PipeWire sound support'
|
||||
printf "%s\n" ' pixman pixman support'
|
||||
printf "%s\n" ' plugins TCG plugins via shared library loading'
|
||||
printf "%s\n" ' png PNG support with libpng'
|
||||
printf "%s\n" ' pvg macOS paravirtualized graphics support'
|
||||
printf "%s\n" ' qatzip QATzip compression support'
|
||||
printf "%s\n" ' qcow1 qcow1 image format support'
|
||||
printf "%s\n" ' qed qed image format support'
|
||||
@@ -198,6 +201,7 @@ meson_options_help() {
|
||||
printf "%s\n" ' u2f U2F emulation support'
|
||||
printf "%s\n" ' uadk UADK Library support'
|
||||
printf "%s\n" ' usb-redir libusbredir support'
|
||||
printf "%s\n" ' valgrind valgrind debug support for coroutine stacks'
|
||||
printf "%s\n" ' vde vde network backend support'
|
||||
printf "%s\n" ' vdi vdi image format support'
|
||||
printf "%s\n" ' vduse-blk-export'
|
||||
@@ -243,10 +247,6 @@ _meson_option_parse() {
|
||||
--audio-drv-list=*) quote_sh "-Daudio_drv_list=$2" ;;
|
||||
--enable-auth-pam) printf "%s" -Dauth_pam=enabled ;;
|
||||
--disable-auth-pam) printf "%s" -Dauth_pam=disabled ;;
|
||||
--enable-avx2) printf "%s" -Davx2=enabled ;;
|
||||
--disable-avx2) printf "%s" -Davx2=disabled ;;
|
||||
--enable-avx512bw) printf "%s" -Davx512bw=enabled ;;
|
||||
--disable-avx512bw) printf "%s" -Davx512bw=disabled ;;
|
||||
--enable-gcov) printf "%s" -Db_coverage=true ;;
|
||||
--disable-gcov) printf "%s" -Db_coverage=false ;;
|
||||
--enable-lto) printf "%s" -Db_lto=true ;;
|
||||
@@ -326,6 +326,7 @@ _meson_option_parse() {
|
||||
--disable-fuzzing) printf "%s" -Dfuzzing=false ;;
|
||||
--enable-gcrypt) printf "%s" -Dgcrypt=enabled ;;
|
||||
--disable-gcrypt) printf "%s" -Dgcrypt=disabled ;;
|
||||
--gdb=*) quote_sh "-Dgdb=$2" ;;
|
||||
--enable-gettext) printf "%s" -Dgettext=enabled ;;
|
||||
--disable-gettext) printf "%s" -Dgettext=disabled ;;
|
||||
--enable-gio) printf "%s" -Dgio=enabled ;;
|
||||
@@ -349,6 +350,8 @@ _meson_option_parse() {
|
||||
--iasl=*) quote_sh "-Diasl=$2" ;;
|
||||
--enable-iconv) printf "%s" -Diconv=enabled ;;
|
||||
--disable-iconv) printf "%s" -Diconv=disabled ;;
|
||||
--enable-igvm) printf "%s" -Digvm=enabled ;;
|
||||
--disable-igvm) printf "%s" -Digvm=disabled ;;
|
||||
--includedir=*) quote_sh "-Dincludedir=$2" ;;
|
||||
--enable-install-blobs) printf "%s" -Dinstall_blobs=true ;;
|
||||
--disable-install-blobs) printf "%s" -Dinstall_blobs=false ;;
|
||||
@@ -407,6 +410,8 @@ _meson_option_parse() {
|
||||
--disable-modules) printf "%s" -Dmodules=disabled ;;
|
||||
--enable-mpath) printf "%s" -Dmpath=enabled ;;
|
||||
--disable-mpath) printf "%s" -Dmpath=disabled ;;
|
||||
--enable-mshv) printf "%s" -Dmshv=enabled ;;
|
||||
--disable-mshv) printf "%s" -Dmshv=disabled ;;
|
||||
--enable-multiprocess) printf "%s" -Dmultiprocess=enabled ;;
|
||||
--disable-multiprocess) printf "%s" -Dmultiprocess=disabled ;;
|
||||
--enable-netmap) printf "%s" -Dnetmap=enabled ;;
|
||||
@@ -425,6 +430,8 @@ _meson_option_parse() {
|
||||
--disable-pa) printf "%s" -Dpa=disabled ;;
|
||||
--enable-parallels) printf "%s" -Dparallels=enabled ;;
|
||||
--disable-parallels) printf "%s" -Dparallels=disabled ;;
|
||||
--enable-passt) printf "%s" -Dpasst=enabled ;;
|
||||
--disable-passt) printf "%s" -Dpasst=disabled ;;
|
||||
--enable-pipewire) printf "%s" -Dpipewire=enabled ;;
|
||||
--disable-pipewire) printf "%s" -Dpipewire=disabled ;;
|
||||
--enable-pixman) printf "%s" -Dpixman=enabled ;;
|
||||
@@ -435,6 +442,8 @@ _meson_option_parse() {
|
||||
--enable-png) printf "%s" -Dpng=enabled ;;
|
||||
--disable-png) printf "%s" -Dpng=disabled ;;
|
||||
--prefix=*) quote_sh "-Dprefix=$2" ;;
|
||||
--enable-pvg) printf "%s" -Dpvg=enabled ;;
|
||||
--disable-pvg) printf "%s" -Dpvg=disabled ;;
|
||||
--enable-qatzip) printf "%s" -Dqatzip=enabled ;;
|
||||
--disable-qatzip) printf "%s" -Dqatzip=disabled ;;
|
||||
--enable-qcow1) printf "%s" -Dqcow1=enabled ;;
|
||||
@@ -524,6 +533,8 @@ _meson_option_parse() {
|
||||
--disable-ubsan) printf "%s" -Dubsan=false ;;
|
||||
--enable-usb-redir) printf "%s" -Dusb_redir=enabled ;;
|
||||
--disable-usb-redir) printf "%s" -Dusb_redir=disabled ;;
|
||||
--enable-valgrind) printf "%s" -Dvalgrind=enabled ;;
|
||||
--disable-valgrind) printf "%s" -Dvalgrind=disabled ;;
|
||||
--enable-vde) printf "%s" -Dvde=enabled ;;
|
||||
--disable-vde) printf "%s" -Dvde=disabled ;;
|
||||
--enable-vdi) printf "%s" -Dvdi=enabled ;;
|
||||
|
||||
@@ -340,7 +340,7 @@ class KconfigParser:
|
||||
|
||||
@classmethod
|
||||
def parse(self, fp, mode=None):
|
||||
data = KconfigData(mode or KconfigParser.defconfig)
|
||||
data = KconfigData(mode or defconfig)
|
||||
parser = KconfigParser(data)
|
||||
parser.parse_file(fp)
|
||||
return data
|
||||
@@ -363,7 +363,9 @@ class KconfigParser:
|
||||
|
||||
def do_assignment(self, var, val):
|
||||
if not var.startswith("CONFIG_"):
|
||||
raise Error('assigned variable should start with CONFIG_')
|
||||
raise KconfigParserError(
|
||||
self, "assigned variable should start with CONFIG_"
|
||||
)
|
||||
var = self.data.do_var(var[7:])
|
||||
self.data.do_assignment(var, val)
|
||||
|
||||
|
||||
Executable
+216
@@ -0,0 +1,216 @@
|
||||
#!/bin/sh -e
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# Create eMMC block device image from boot, RPMB and user data images
|
||||
#
|
||||
# Copyright (c) Siemens, 2025
|
||||
#
|
||||
# Authors:
|
||||
# Jan Kiszka <jan.kiszka@siemens.com>
|
||||
#
|
||||
|
||||
usage() {
|
||||
echo "$0 [OPTIONS] USER_IMG[:SIZE] OUTPUT_IMG"
|
||||
echo ""
|
||||
echo "SIZE must be a power of 2 up to 2G and multiples of 512 byte from there on."
|
||||
echo "If no SIZE is specified, the size of USER_ING will be used (rounded up)."
|
||||
echo ""
|
||||
echo "Supported options:"
|
||||
echo " -b BOOT1_IMG[:SIZE] Add boot partitions. SIZE must be multiples of 128K. If"
|
||||
echo " no SIZE is specified, the size of BOOT1_IMG will be"
|
||||
echo " used (rounded up). BOOT1_IMG will be stored in boot"
|
||||
echo " partition 1, and a boot partition 2 of the same size"
|
||||
echo " will be created as empty (all zeros) unless -B is"
|
||||
echo " specified as well."
|
||||
echo " -B BOOT2_IMG Fill boot partition 2 with BOOT2_IMG. Must be combined"
|
||||
echo " with -b which is also defining the partition size."
|
||||
echo " -r RPMB_IMG[:SIZE] Add RPMB partition. SIZE must be multiples of 128K. If"
|
||||
echo " no SIZE is specified, the size of RPMB_IMG will be"
|
||||
echo " used (rounded up)."
|
||||
echo " -h, --help This help"
|
||||
echo ""
|
||||
echo "All SIZE parameters support the units K, M, G. If SIZE is smaller than the"
|
||||
echo "associated image, it will be truncated in the output image."
|
||||
exit "$1"
|
||||
}
|
||||
|
||||
process_size() {
|
||||
name=$1
|
||||
image_file=$2
|
||||
alignment=$3
|
||||
image_arg=$4
|
||||
if [ "${image_arg#*:}" = "$image_arg" ]; then
|
||||
if ! size=$(wc -c < "$image_file" 2>/dev/null); then
|
||||
echo "Missing $name image '$image_file'." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ "$alignment" = 128 ]; then
|
||||
size=$(( (size + 128 * 1024 - 1) & ~(128 * 1024 - 1) ))
|
||||
elif [ $size -gt $((2 * 1024 * 1024 * 1024)) ]; then
|
||||
size=$(( (size + 511) & ~511 ))
|
||||
elif [ $(( size & (size - 1) )) -gt 0 ]; then
|
||||
n=0
|
||||
while [ "$size" -gt 0 ]; do
|
||||
size=$((size >> 1))
|
||||
n=$((n + 1))
|
||||
done
|
||||
size=$((1 << n))
|
||||
fi
|
||||
else
|
||||
value="${image_arg#*:}"
|
||||
if [ "${value%K}" != "$value" ]; then
|
||||
size=${value%K}
|
||||
multiplier=1024
|
||||
elif [ "${value%M}" != "$value" ]; then
|
||||
size=${value%M}
|
||||
multiplier=$((1024 * 1024))
|
||||
elif [ "${value%G}" != "$value" ]; then
|
||||
size=${value%G}
|
||||
multiplier=$((1024 * 1024 * 1024))
|
||||
else
|
||||
size=$value
|
||||
multiplier=1
|
||||
fi
|
||||
# check if "$size" is a valid integer by doing a self-comparison
|
||||
if [ "$size" -eq "$size" ] 2>/dev/null; then
|
||||
size=$((size * multiplier))
|
||||
else
|
||||
echo "Invalid value '$value' specified for $image_file image size." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ "$alignment" = 128 ]; then
|
||||
if [ $(( size & (128 * 1024 - 1) )) -ne 0 ]; then
|
||||
echo "The $name image size must be multiples of 128K." >&2
|
||||
exit 1
|
||||
fi
|
||||
elif [ $size -gt $((2 * 1024 * 1024 * 1024)) ]; then
|
||||
if [ $(( size & 511)) -ne 0 ]; then
|
||||
echo "The $name image size must be multiples of 512 (if >2G)." >&2
|
||||
exit 1
|
||||
fi
|
||||
elif [ $(( size & (size - 1) )) -gt 0 ]; then
|
||||
echo "The $name image size must be power of 2 (up to 2G)." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
echo $size
|
||||
}
|
||||
|
||||
check_truncation() {
|
||||
image_file=$1
|
||||
output_size=$2
|
||||
if [ "$image_file" = "/dev/zero" ]; then
|
||||
return
|
||||
fi
|
||||
if ! actual_size=$(wc -c < "$image_file" 2>/dev/null); then
|
||||
echo "Missing image '$image_file'." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ "$actual_size" -gt "$output_size" ]; then
|
||||
echo "Warning: image '$image_file' will be truncated on output."
|
||||
fi
|
||||
}
|
||||
|
||||
userimg=
|
||||
outimg=
|
||||
bootimg1=
|
||||
bootimg2=/dev/zero
|
||||
bootsz=0
|
||||
rpmbimg=
|
||||
rpmbsz=0
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-b)
|
||||
shift
|
||||
[ $# -ge 1 ] || usage 1
|
||||
bootimg1=${1%%:*}
|
||||
bootsz=$(process_size boot "$bootimg1" 128 "$1")
|
||||
shift
|
||||
;;
|
||||
-B)
|
||||
shift
|
||||
[ $# -ge 1 ] || usage 1
|
||||
bootimg2=$1
|
||||
shift
|
||||
;;
|
||||
-r)
|
||||
shift
|
||||
[ $# -ge 1 ] || usage 1
|
||||
rpmbimg=${1%%:*}
|
||||
rpmbsz=$(process_size RPMB "$rpmbimg" 128 "$1")
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage 0
|
||||
;;
|
||||
*)
|
||||
if [ -z "$userimg" ]; then
|
||||
userimg=${1%%:*}
|
||||
usersz=$(process_size user "$userimg" U "$1")
|
||||
elif [ -z "$outimg" ]; then
|
||||
outimg=$1
|
||||
else
|
||||
usage 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ -n "$outimg" ] || usage 1
|
||||
|
||||
if [ "$bootsz" -gt $((32640 * 1024)) ]; then
|
||||
echo "Boot image size is larger than 32640K." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ "$rpmbsz" -gt $((16384 * 1024)) ]; then
|
||||
echo "RPMB image size is larger than 16384K." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Creating eMMC image"
|
||||
|
||||
truncate -s 0 "$outimg"
|
||||
pos=0
|
||||
|
||||
if [ "$bootsz" -gt 0 ]; then
|
||||
echo " Boot partition 1 and 2: $((bootsz / 1024))K each"
|
||||
blocks=$(( bootsz / (128 * 1024) ))
|
||||
check_truncation "$bootimg1" "$bootsz"
|
||||
dd if="$bootimg1" of="$outimg" conv=sparse bs=128K count=$blocks \
|
||||
status=none
|
||||
check_truncation "$bootimg2" "$bootsz"
|
||||
dd if="$bootimg2" of="$outimg" conv=sparse bs=128K count=$blocks \
|
||||
seek=$blocks status=none
|
||||
pos=$((2 * bootsz))
|
||||
fi
|
||||
|
||||
if [ "$rpmbsz" -gt 0 ]; then
|
||||
echo " RPMB partition: $((rpmbsz / 1024))K"
|
||||
blocks=$(( rpmbsz / (128 * 1024) ))
|
||||
check_truncation "$rpmbimg" "$rpmbsz"
|
||||
dd if="$rpmbimg" of="$outimg" conv=sparse bs=128K count=$blocks \
|
||||
seek=$(( pos / (128 * 1024) )) status=none
|
||||
pos=$((pos + rpmbsz))
|
||||
fi
|
||||
|
||||
if [ "$usersz" -lt 1024 ]; then
|
||||
echo " User data: $usersz bytes"
|
||||
elif [ "$usersz" -lt $((1024 * 1024)) ]; then
|
||||
echo " User data: $(( (usersz + 1023) / 1024 ))K ($usersz)"
|
||||
elif [ "$usersz" -lt $((1024 * 1024 * 1024)) ]; then
|
||||
echo " User data: $(( (usersz + 1048575) / 1048576))M ($usersz)"
|
||||
else
|
||||
echo " User data: $(( (usersz + 1073741823) / 1073741824))G ($usersz)"
|
||||
fi
|
||||
check_truncation "$userimg" "$usersz"
|
||||
dd if="$userimg" of="$outimg" conv=sparse bs=128K seek=$(( pos / (128 * 1024) )) \
|
||||
count=$(( (usersz + 128 * 1024 - 1) / (128 * 1024) )) status=none
|
||||
pos=$((pos + usersz))
|
||||
truncate -s $pos "$outimg"
|
||||
|
||||
echo ""
|
||||
echo "Instantiate by appending to the qemu command line:"
|
||||
echo " -drive file=$outimg,if=none,format=raw,id=emmc-img"
|
||||
echo " -device emmc,boot-partition-size=$bootsz,rpmb-partition-size=$rpmbsz,drive=emmc-img"
|
||||
+11
-13
@@ -1,5 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import sys
|
||||
@@ -7,15 +6,6 @@ import json
|
||||
import shlex
|
||||
import subprocess
|
||||
|
||||
def find_command(src, target, compile_commands):
|
||||
for command in compile_commands:
|
||||
if command['file'] != src:
|
||||
continue
|
||||
if target != '' and command['command'].find(target) == -1:
|
||||
continue
|
||||
return command['command']
|
||||
return 'false'
|
||||
|
||||
def process_command(src, command):
|
||||
skip = False
|
||||
out = []
|
||||
@@ -43,14 +33,22 @@ def main(args):
|
||||
print("MODINFO_DEBUG target %s" % target)
|
||||
arch = target[:-8] # cut '-softmmu'
|
||||
print("MODINFO_START arch \"%s\" MODINFO_END" % arch)
|
||||
|
||||
with open('compile_commands.json') as f:
|
||||
compile_commands = json.load(f)
|
||||
for src in args:
|
||||
compile_commands_json = json.load(f)
|
||||
compile_commands = { x['output']: x for x in compile_commands_json }
|
||||
|
||||
for obj in args:
|
||||
entry = compile_commands.get(obj, None)
|
||||
if not entry:
|
||||
sys.stderr.write(f'modinfo: Could not find object file {obj}')
|
||||
sys.exit(1)
|
||||
src = entry['file']
|
||||
if not src.endswith('.c'):
|
||||
print("MODINFO_DEBUG skip %s" % src)
|
||||
continue
|
||||
command = entry['command']
|
||||
print("MODINFO_DEBUG src %s" % src)
|
||||
command = find_command(src, target, compile_commands)
|
||||
cmdline = process_command(src, command)
|
||||
print("MODINFO_DEBUG cmd", cmdline)
|
||||
result = subprocess.run(cmdline, stdout = subprocess.PIPE,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
+23
-25
@@ -8,34 +8,35 @@ from collections import defaultdict
|
||||
import itertools
|
||||
import json
|
||||
import os
|
||||
import shlex
|
||||
import sys
|
||||
|
||||
class Suite(object):
|
||||
def __init__(self):
|
||||
self.deps = set()
|
||||
self.speeds = ['quick']
|
||||
self.speeds = set()
|
||||
|
||||
def names(self, base):
|
||||
return [base if speed == 'quick' else f'{base}-{speed}' for speed in self.speeds]
|
||||
return [f'{base}-{speed}' for speed in self.speeds]
|
||||
|
||||
|
||||
print('''
|
||||
print(r'''
|
||||
SPEED = quick
|
||||
|
||||
.speed.quick = $(foreach s,$(sort $(filter-out %-slow %-thorough, $1)), --suite $s)
|
||||
.speed.slow = $(foreach s,$(sort $(filter-out %-thorough, $1)), --suite $s)
|
||||
.speed.thorough = $(foreach s,$(sort $1), --suite $s)
|
||||
.speed.quick = $(sort $(filter-out %-slow %-thorough, $1))
|
||||
.speed.slow = $(sort $(filter-out %-thorough, $1))
|
||||
.speed.thorough = $(sort $1)
|
||||
|
||||
TIMEOUT_MULTIPLIER = 1
|
||||
TIMEOUT_MULTIPLIER ?= 1
|
||||
.mtestargs = --no-rebuild -t $(TIMEOUT_MULTIPLIER)
|
||||
ifneq ($(SPEED), quick)
|
||||
.mtestargs += --setup $(SPEED)
|
||||
endif
|
||||
.mtestargs += $(subst -j,--num-processes , $(filter-out -j, $(lastword -j1 $(filter -j%, $(MAKEFLAGS)))))
|
||||
|
||||
.check.mtestargs = $(MTESTARGS) $(.mtestargs) $(if $(V),--verbose,--print-errorlogs)
|
||||
.bench.mtestargs = $(MTESTARGS) $(.mtestargs) --benchmark --verbose''')
|
||||
.check.mtestargs = $(MTESTARGS) $(.mtestargs) $(if $(V),--verbose,--print-errorlogs) \
|
||||
$(foreach s, $(sort $(.check.mtest-suites)), --suite $s)
|
||||
.bench.mtestargs = $(MTESTARGS) $(.mtestargs) --benchmark --verbose \
|
||||
$(foreach s, $(sort $(.bench.mtest-suites)), --suite $s)''')
|
||||
|
||||
introspect = json.load(sys.stdin)
|
||||
|
||||
@@ -57,13 +58,13 @@ def process_tests(test, targets, suites):
|
||||
s = s.split(':')[1]
|
||||
if s == 'slow' or s == 'thorough':
|
||||
continue
|
||||
suites[s].deps.update(deps)
|
||||
if s.endswith('-slow'):
|
||||
s = s[:-5]
|
||||
suites[s].speeds.append('slow')
|
||||
suites[s].speeds.add('slow')
|
||||
if s.endswith('-thorough'):
|
||||
s = s[:-9]
|
||||
suites[s].speeds.append('thorough')
|
||||
suites[s].deps.update(deps)
|
||||
suites[s].speeds.add('thorough')
|
||||
|
||||
def emit_prolog(suites, prefix):
|
||||
all_targets = ' '.join((f'{prefix}-{k}' for k in suites.keys()))
|
||||
@@ -72,29 +73,26 @@ def emit_prolog(suites, prefix):
|
||||
print(f'all-{prefix}-targets = {all_targets}')
|
||||
print(f'all-{prefix}-xml = {all_xml}')
|
||||
print(f'.PHONY: {prefix} do-meson-{prefix} {prefix}-report.junit.xml $(all-{prefix}-targets) $(all-{prefix}-xml)')
|
||||
print(f'ifeq ($(filter {prefix}, $(MAKECMDGOALS)),)')
|
||||
print(f'.{prefix}.mtestargs += $(call .speed.$(SPEED), $(.{prefix}.mtest-suites))')
|
||||
print(f'endif')
|
||||
print(f'ninja-cmd-goals += $(foreach s, $(.{prefix}.mtest-suites), $(.{prefix}-$s.deps))')
|
||||
print(f'{prefix}-build: run-ninja')
|
||||
print(f'{prefix} $(all-{prefix}-targets): do-meson-{prefix}')
|
||||
print(f'do-meson-{prefix}: run-ninja; $(if $(MAKE.n),,+)$(MESON) test $(.{prefix}.mtestargs)')
|
||||
print(f'{prefix}-report.junit.xml $(all-{prefix}-xml): {prefix}-report%.junit.xml: run-ninja')
|
||||
print(f'\t$(MAKE) {prefix}$* MTESTARGS="$(MTESTARGS) --logbase {prefix}-report$*" && ln -f meson-logs/$@ .')
|
||||
|
||||
def emit_suite_deps(name, suite, prefix):
|
||||
def emit_suite(name, suite, prefix):
|
||||
deps = ' '.join(suite.deps)
|
||||
targets = [f'{prefix}-{name}', f'{prefix}-report-{name}.junit.xml', f'{prefix}', f'{prefix}-report.junit.xml',
|
||||
f'{prefix}-build']
|
||||
print()
|
||||
print(f'.{prefix}-{name}.deps = {deps}')
|
||||
for t in targets:
|
||||
print(f'.ninja-goals.{t} += $(.{prefix}-{name}.deps)')
|
||||
print(f'.ninja-goals.check-build += $(.{prefix}-{name}.deps)')
|
||||
|
||||
def emit_suite(name, suite, prefix):
|
||||
emit_suite_deps(name, suite, prefix)
|
||||
targets = f'{prefix}-{name} {prefix}-report-{name}.junit.xml {prefix} {prefix}-report.junit.xml'
|
||||
names = ' '.join(suite.names(name))
|
||||
targets = f'{prefix}-{name} {prefix}-report-{name}.junit.xml'
|
||||
if not name.endswith('-slow') and not name.endswith('-thorough'):
|
||||
targets += f' {prefix} {prefix}-report.junit.xml'
|
||||
print(f'ifneq ($(filter {targets}, $(MAKECMDGOALS)),)')
|
||||
print(f'.{prefix}.mtest-suites += ' + ' '.join(suite.names(name)))
|
||||
# for the "base" suite possibly add FOO-slow and FOO-thorough
|
||||
print(f".{prefix}.mtest-suites += {name} $(call .speed.$(SPEED), {names})")
|
||||
print(f'endif')
|
||||
|
||||
targets = {t['id']: [os.path.relpath(f) for f in t['filename']]
|
||||
|
||||
+7
-7
@@ -23,7 +23,7 @@ def find_deps(exe_or_dll, search_path, analyzed_deps):
|
||||
output = subprocess.check_output(["objdump", "-p", exe_or_dll], text=True)
|
||||
output = output.split("\n")
|
||||
for line in output:
|
||||
if not line.startswith("\tDLL Name: "):
|
||||
if not line.lstrip().startswith("DLL Name: "):
|
||||
continue
|
||||
|
||||
dep = line.split("DLL Name: ")[1].strip()
|
||||
@@ -37,10 +37,10 @@ def find_deps(exe_or_dll, search_path, analyzed_deps):
|
||||
|
||||
analyzed_deps.add(dep)
|
||||
# locate the dll dependencies recursively
|
||||
rdeps = find_deps(dll, search_path, analyzed_deps)
|
||||
analyzed_deps, rdeps = find_deps(dll, search_path, analyzed_deps)
|
||||
deps.extend(rdeps)
|
||||
|
||||
return deps
|
||||
return analyzed_deps, deps
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="QEMU NSIS build helper.")
|
||||
@@ -92,18 +92,18 @@ def main():
|
||||
dlldir = os.path.join(destdir + prefix, "dll")
|
||||
os.mkdir(dlldir)
|
||||
|
||||
analyzed_deps = set()
|
||||
for exe in glob.glob(os.path.join(destdir + prefix, "*.exe")):
|
||||
signcode(exe)
|
||||
|
||||
# find all dll dependencies
|
||||
deps = set(find_deps(exe, search_path, set()))
|
||||
analyzed_deps, deps = find_deps(exe, search_path, analyzed_deps)
|
||||
deps = set(deps)
|
||||
deps.remove(exe)
|
||||
|
||||
# copy all dlls to the DLLDIR
|
||||
for dep in deps:
|
||||
dllfile = os.path.join(dlldir, os.path.basename(dep))
|
||||
if (os.path.exists(dllfile)):
|
||||
continue
|
||||
print("Copying '%s' to '%s'" % (dep, dllfile))
|
||||
shutil.copy(dep, dllfile)
|
||||
|
||||
@@ -114,7 +114,7 @@ def main():
|
||||
"-DSRCDIR=" + args.srcdir,
|
||||
"-DBINDIR=" + destdir + prefix,
|
||||
]
|
||||
if args.cpu == "x86_64":
|
||||
if args.cpu == "aarch64" or args.cpu == "x86_64":
|
||||
makensis += ["-DW64"]
|
||||
makensis += ["-DDLLDIR=" + dlldir]
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
This takes a crashing qtest trace and tries to remove superfluous operations
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Convert plain qtest traces to C or Bash reproducers
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Use this to convert qtest log info from a generic fuzzer input into a qtest
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
# coding: utf-8
|
||||
#
|
||||
# Probe gdb for supported architectures.
|
||||
#
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
[flake8]
|
||||
# Prefer pylint's bare-except checks to flake8's
|
||||
extend-ignore = E722
|
||||
@@ -1,7 +0,0 @@
|
||||
[settings]
|
||||
force_grid_wrap=4
|
||||
force_sort_within_sections=True
|
||||
include_trailing_comma=True
|
||||
line_length=72
|
||||
lines_after_imports=2
|
||||
multi_line_output=3
|
||||
@@ -0,0 +1,65 @@
|
||||
# This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
# See the COPYING file in the top-level directory.
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from .commands import gen_commands
|
||||
from .events import gen_events
|
||||
from .features import gen_features
|
||||
from .introspect import gen_introspect
|
||||
from .schema import QAPISchema
|
||||
from .types import gen_types
|
||||
from .visit import gen_visit
|
||||
|
||||
|
||||
class QAPIBackend(ABC):
|
||||
# pylint: disable=too-few-public-methods
|
||||
|
||||
@abstractmethod
|
||||
def generate(self,
|
||||
schema: QAPISchema,
|
||||
output_dir: str,
|
||||
prefix: str,
|
||||
unmask: bool,
|
||||
builtins: bool,
|
||||
gen_tracing: bool) -> None:
|
||||
"""
|
||||
Generate code for the given schema into the target directory.
|
||||
|
||||
:param schema: The primary QAPI schema object.
|
||||
:param output_dir: The output directory to store generated code.
|
||||
:param prefix: Optional C-code prefix for symbol names.
|
||||
:param unmask: Expose non-ABI names through introspection?
|
||||
:param builtins: Generate code for built-in types?
|
||||
|
||||
:raise QAPIError: On failures.
|
||||
"""
|
||||
|
||||
|
||||
class QAPICBackend(QAPIBackend):
|
||||
# pylint: disable=too-few-public-methods
|
||||
|
||||
def generate(self,
|
||||
schema: QAPISchema,
|
||||
output_dir: str,
|
||||
prefix: str,
|
||||
unmask: bool,
|
||||
builtins: bool,
|
||||
gen_tracing: bool) -> None:
|
||||
"""
|
||||
Generate C code for the given schema into the target directory.
|
||||
|
||||
:param schema_file: The primary QAPI schema file.
|
||||
:param output_dir: The output directory to store generated code.
|
||||
:param prefix: Optional C-code prefix for symbol names.
|
||||
:param unmask: Expose non-ABI names through introspection?
|
||||
:param builtins: Generate code for built-in types?
|
||||
|
||||
:raise QAPIError: On failures.
|
||||
"""
|
||||
gen_types(schema, output_dir, prefix, builtins)
|
||||
gen_features(schema, output_dir, prefix)
|
||||
gen_visit(schema, output_dir, prefix, builtins)
|
||||
gen_commands(schema, output_dir, prefix, gen_tracing)
|
||||
gen_events(schema, output_dir, prefix)
|
||||
gen_introspect(schema, output_dir, prefix, unmask)
|
||||
+17
-43
@@ -13,19 +13,13 @@ This work is licensed under the terms of the GNU GPL, version 2.
|
||||
See the COPYING file in the top-level directory.
|
||||
"""
|
||||
|
||||
from typing import (
|
||||
Dict,
|
||||
List,
|
||||
Optional,
|
||||
Set,
|
||||
)
|
||||
from typing import List, Optional
|
||||
|
||||
from .common import c_name, mcgen
|
||||
from .gen import (
|
||||
QAPIGenC,
|
||||
QAPISchemaModularCVisitor,
|
||||
build_params,
|
||||
gen_special_features,
|
||||
gen_features,
|
||||
ifcontext,
|
||||
)
|
||||
from .schema import (
|
||||
@@ -112,11 +106,7 @@ def gen_call(name: str,
|
||||
''')
|
||||
|
||||
if ret_type:
|
||||
ret += mcgen('''
|
||||
|
||||
qmp_marshal_output_%(c_name)s(retval, ret, errp);
|
||||
''',
|
||||
c_name=ret_type.c_name())
|
||||
ret += gen_marshal_output(ret_type)
|
||||
|
||||
if gen_tracing:
|
||||
if ret_type:
|
||||
@@ -142,22 +132,16 @@ def gen_call(name: str,
|
||||
def gen_marshal_output(ret_type: QAPISchemaType) -> str:
|
||||
return mcgen('''
|
||||
|
||||
static void qmp_marshal_output_%(c_name)s(%(c_type)s ret_in,
|
||||
QObject **ret_out, Error **errp)
|
||||
{
|
||||
Visitor *v;
|
||||
|
||||
v = qobject_output_visitor_new_qmp(ret_out);
|
||||
if (visit_type_%(c_name)s(v, "unused", &ret_in, errp)) {
|
||||
visit_complete(v, ret_out);
|
||||
ov = qobject_output_visitor_new_qmp(ret);
|
||||
if (visit_type_%(c_name)s(ov, "unused", &retval, errp)) {
|
||||
visit_complete(ov, ret);
|
||||
}
|
||||
visit_free(v);
|
||||
v = qapi_dealloc_visitor_new();
|
||||
visit_type_%(c_name)s(v, "unused", &ret_in, NULL);
|
||||
visit_free(v);
|
||||
}
|
||||
visit_free(ov);
|
||||
ov = qapi_dealloc_visitor_new();
|
||||
visit_type_%(c_name)s(ov, "unused", &retval, NULL);
|
||||
visit_free(ov);
|
||||
''',
|
||||
c_type=ret_type.c_type(), c_name=ret_type.c_name())
|
||||
c_name=ret_type.c_name())
|
||||
|
||||
|
||||
def build_marshal_proto(name: str,
|
||||
@@ -209,6 +193,7 @@ def gen_marshal(name: str,
|
||||
if ret_type:
|
||||
ret += mcgen('''
|
||||
%(c_type)s retval;
|
||||
Visitor *ov;
|
||||
''',
|
||||
c_type=ret_type.c_type())
|
||||
|
||||
@@ -298,7 +283,7 @@ def gen_register_command(name: str,
|
||||
''',
|
||||
name=name, c_name=c_name(name),
|
||||
opts=' | '.join(options) or 0,
|
||||
feats=gen_special_features(features))
|
||||
feats=gen_features(features))
|
||||
return ret
|
||||
|
||||
|
||||
@@ -308,11 +293,9 @@ class QAPISchemaGenCommandVisitor(QAPISchemaModularCVisitor):
|
||||
prefix, 'qapi-commands',
|
||||
' * Schema-defined QAPI/QMP commands', None, __doc__,
|
||||
gen_tracing=gen_tracing)
|
||||
self._visited_ret_types: Dict[QAPIGenC, Set[QAPISchemaType]] = {}
|
||||
self._gen_tracing = gen_tracing
|
||||
|
||||
def _begin_user_module(self, name: str) -> None:
|
||||
self._visited_ret_types[self._genc] = set()
|
||||
commands = self._module_basename('qapi-commands', name)
|
||||
types = self._module_basename('qapi-types', name)
|
||||
visit = self._module_basename('qapi-visit', name)
|
||||
@@ -320,7 +303,7 @@ class QAPISchemaGenCommandVisitor(QAPISchemaModularCVisitor):
|
||||
#include "qemu/osdep.h"
|
||||
#include "qapi/compat-policy.h"
|
||||
#include "qapi/visitor.h"
|
||||
#include "qapi/qmp/qdict.h"
|
||||
#include "qobject/qdict.h"
|
||||
#include "qapi/dealloc-visitor.h"
|
||||
#include "qapi/error.h"
|
||||
#include "%(visit)s.h"
|
||||
@@ -330,7 +313,7 @@ class QAPISchemaGenCommandVisitor(QAPISchemaModularCVisitor):
|
||||
|
||||
if self._gen_tracing and commands != 'qapi-commands':
|
||||
self._genc.add(mcgen('''
|
||||
#include "qapi/qmp/qjson.h"
|
||||
#include "qobject/qjson.h"
|
||||
#include "trace/trace-%(nm)s_trace_events.h"
|
||||
''',
|
||||
nm=c_name(commands, protect=False)))
|
||||
@@ -346,7 +329,7 @@ class QAPISchemaGenCommandVisitor(QAPISchemaModularCVisitor):
|
||||
def visit_begin(self, schema: QAPISchema) -> None:
|
||||
self._add_module('./init', ' * QAPI Commands initialization')
|
||||
self._genh.add(mcgen('''
|
||||
#include "qapi/qmp/dispatch.h"
|
||||
#include "qapi/qmp-registry.h"
|
||||
|
||||
void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds);
|
||||
''',
|
||||
@@ -355,6 +338,7 @@ void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds);
|
||||
#include "qemu/osdep.h"
|
||||
#include "%(prefix)sqapi-commands.h"
|
||||
#include "%(prefix)sqapi-init-commands.h"
|
||||
#include "%(prefix)sqapi-features.h"
|
||||
|
||||
void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds)
|
||||
{
|
||||
@@ -385,16 +369,6 @@ void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds)
|
||||
coroutine: bool) -> None:
|
||||
if not gen:
|
||||
return
|
||||
# FIXME: If T is a user-defined type, the user is responsible
|
||||
# for making this work, i.e. to make T's condition the
|
||||
# conjunction of the T-returning commands' conditions. If T
|
||||
# is a built-in type, this isn't possible: the
|
||||
# qmp_marshal_output_T() will be generated unconditionally.
|
||||
if ret_type and ret_type not in self._visited_ret_types[self._genc]:
|
||||
self._visited_ret_types[self._genc].add(ret_type)
|
||||
with ifcontext(ret_type.ifcond,
|
||||
self._genh, self._genc):
|
||||
self._genc.add(gen_marshal_output(ret_type))
|
||||
with ifcontext(ifcond, self._genh, self._genc):
|
||||
self._genh.add(gen_command_decl(name, arg_type, boxed,
|
||||
ret_type, coroutine))
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2017-2019 Red Hat Inc.
|
||||
#
|
||||
# Authors:
|
||||
|
||||
@@ -194,7 +194,7 @@ class QAPISchemaGenEventVisitor(QAPISchemaModularCVisitor):
|
||||
#include "%(visit)s.h"
|
||||
#include "qapi/compat-policy.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qapi/qmp/qdict.h"
|
||||
#include "qobject/qdict.h"
|
||||
#include "qapi/qmp-event.h"
|
||||
''',
|
||||
events=events, visit=visit,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright IBM, Corp. 2011
|
||||
# Copyright (c) 2013-2021 Red Hat Inc.
|
||||
#
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
"""
|
||||
QAPI features generator
|
||||
|
||||
Copyright 2024 Red Hat
|
||||
|
||||
This work is licensed under the terms of the GNU GPL, version 2.
|
||||
# See the COPYING file in the top-level directory.
|
||||
"""
|
||||
|
||||
from typing import ValuesView
|
||||
|
||||
from .common import c_enum_const, c_name
|
||||
from .gen import QAPISchemaMonolithicCVisitor
|
||||
from .schema import QAPISchema, QAPISchemaFeature
|
||||
|
||||
|
||||
class QAPISchemaGenFeatureVisitor(QAPISchemaMonolithicCVisitor):
|
||||
|
||||
def __init__(self, prefix: str):
|
||||
super().__init__(
|
||||
prefix, 'qapi-features',
|
||||
' * Schema-defined QAPI features',
|
||||
__doc__)
|
||||
|
||||
self.features: ValuesView[QAPISchemaFeature]
|
||||
|
||||
def visit_begin(self, schema: QAPISchema) -> None:
|
||||
self.features = schema.features()
|
||||
self._genh.add("#include \"qapi/util.h\"\n\n")
|
||||
|
||||
def visit_end(self) -> None:
|
||||
self._genh.add("typedef enum {\n")
|
||||
for f in self.features:
|
||||
self._genh.add(f" {c_enum_const('qapi_feature', f.name)}")
|
||||
if f.name in QAPISchemaFeature.SPECIAL_NAMES:
|
||||
self._genh.add(f" = {c_enum_const('qapi', f.name)},\n")
|
||||
else:
|
||||
self._genh.add(",\n")
|
||||
|
||||
self._genh.add("} " + c_name('QapiFeature') + ";\n")
|
||||
|
||||
|
||||
def gen_features(schema: QAPISchema,
|
||||
output_dir: str,
|
||||
prefix: str) -> None:
|
||||
vis = QAPISchemaGenFeatureVisitor(prefix)
|
||||
schema.visit(vis)
|
||||
vis.write(output_dir)
|
||||
+5
-6
@@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# QAPI code generation
|
||||
#
|
||||
# Copyright (c) 2015-2019 Red Hat Inc.
|
||||
@@ -24,6 +22,7 @@ from typing import (
|
||||
)
|
||||
|
||||
from .common import (
|
||||
c_enum_const,
|
||||
c_fname,
|
||||
c_name,
|
||||
guardend,
|
||||
@@ -40,10 +39,10 @@ from .schema import (
|
||||
from .source import QAPISourceInfo
|
||||
|
||||
|
||||
def gen_special_features(features: Sequence[QAPISchemaFeature]) -> str:
|
||||
special_features = [f"1u << QAPI_{feat.name.upper()}"
|
||||
for feat in features if feat.is_special()]
|
||||
return ' | '.join(special_features) or '0'
|
||||
def gen_features(features: Sequence[QAPISchemaFeature]) -> str:
|
||||
feats = [f"1u << {c_enum_const('qapi_feature', feat.name)}"
|
||||
for feat in features]
|
||||
return ' | '.join(feats) or '0'
|
||||
|
||||
|
||||
class QAPIGen:
|
||||
|
||||
@@ -11,6 +11,7 @@ This work is licensed under the terms of the GNU GPL, version 2.
|
||||
See the COPYING file in the top-level directory.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
@@ -58,7 +59,7 @@ from .source import QAPISourceInfo
|
||||
#
|
||||
# Sadly, mypy does not support recursive types; so the _Stub alias is used to
|
||||
# mark the imprecision in the type model where we'd otherwise use JSONValue.
|
||||
_Stub = Any
|
||||
_Stub = Any # pylint: disable=invalid-name
|
||||
_Scalar = Union[str, bool, None]
|
||||
_NonScalar = Union[Dict[str, _Stub], List[_Stub]]
|
||||
_Value = Union[_Scalar, _NonScalar]
|
||||
@@ -79,19 +80,16 @@ SchemaInfoCommand = Dict[str, object]
|
||||
_ValueT = TypeVar('_ValueT', bound=_Value)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Annotated(Generic[_ValueT]):
|
||||
"""
|
||||
Annotated generally contains a SchemaInfo-like type (as a dict),
|
||||
But it also used to wrap comments/ifconds around scalar leaf values,
|
||||
for the benefit of features and enums.
|
||||
"""
|
||||
# TODO: Remove after Python 3.7 adds @dataclass:
|
||||
# pylint: disable=too-few-public-methods
|
||||
def __init__(self, value: _ValueT, ifcond: QAPISchemaIfCond,
|
||||
comment: Optional[str] = None):
|
||||
self.value = value
|
||||
self.comment: Optional[str] = comment
|
||||
self.ifcond = ifcond
|
||||
value: _ValueT
|
||||
ifcond: QAPISchemaIfCond
|
||||
comment: Optional[str] = None
|
||||
|
||||
|
||||
def _tree_to_qlit(obj: JSONValue,
|
||||
@@ -197,7 +195,7 @@ class QAPISchemaGenIntrospectVisitor(QAPISchemaMonolithicCVisitor):
|
||||
# generate C
|
||||
name = c_name(self._prefix, protect=False) + 'qmp_schema_qlit'
|
||||
self._genh.add(mcgen('''
|
||||
#include "qapi/qmp/qlit.h"
|
||||
#include "qobject/qlit.h"
|
||||
|
||||
extern const QLitObject %(c_name)s;
|
||||
''',
|
||||
|
||||
+39
-33
@@ -8,17 +8,14 @@ This is the main entry point for generating C code from the QAPI schema.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
from importlib import import_module
|
||||
import sys
|
||||
from typing import Optional
|
||||
|
||||
from .commands import gen_commands
|
||||
from .backend import QAPIBackend, QAPICBackend
|
||||
from .common import must_match
|
||||
from .error import QAPIError
|
||||
from .events import gen_events
|
||||
from .introspect import gen_introspect
|
||||
from .schema import QAPISchema
|
||||
from .types import gen_types
|
||||
from .visit import gen_visit
|
||||
|
||||
|
||||
def invalid_prefix_char(prefix: str) -> Optional[str]:
|
||||
@@ -28,31 +25,36 @@ def invalid_prefix_char(prefix: str) -> Optional[str]:
|
||||
return None
|
||||
|
||||
|
||||
def generate(schema_file: str,
|
||||
output_dir: str,
|
||||
prefix: str,
|
||||
unmask: bool = False,
|
||||
builtins: bool = False,
|
||||
gen_tracing: bool = False) -> None:
|
||||
"""
|
||||
Generate C code for the given schema into the target directory.
|
||||
def create_backend(path: str) -> QAPIBackend:
|
||||
if path is None:
|
||||
return QAPICBackend()
|
||||
|
||||
:param schema_file: The primary QAPI schema file.
|
||||
:param output_dir: The output directory to store generated code.
|
||||
:param prefix: Optional C-code prefix for symbol names.
|
||||
:param unmask: Expose non-ABI names through introspection?
|
||||
:param builtins: Generate code for built-in types?
|
||||
module_path, dot, class_name = path.rpartition('.')
|
||||
if not dot:
|
||||
raise QAPIError("argument of -B must be of the form MODULE.CLASS")
|
||||
|
||||
:raise QAPIError: On failures.
|
||||
"""
|
||||
assert invalid_prefix_char(prefix) is None
|
||||
try:
|
||||
mod = import_module(module_path)
|
||||
except Exception as ex:
|
||||
raise QAPIError(f"unable to import '{module_path}': {ex}") from ex
|
||||
|
||||
schema = QAPISchema(schema_file)
|
||||
gen_types(schema, output_dir, prefix, builtins)
|
||||
gen_visit(schema, output_dir, prefix, builtins)
|
||||
gen_commands(schema, output_dir, prefix, gen_tracing)
|
||||
gen_events(schema, output_dir, prefix)
|
||||
gen_introspect(schema, output_dir, prefix, unmask)
|
||||
try:
|
||||
klass = getattr(mod, class_name)
|
||||
except AttributeError as ex:
|
||||
raise QAPIError(
|
||||
f"module '{module_path}' has no class '{class_name}'") from ex
|
||||
|
||||
try:
|
||||
backend = klass()
|
||||
except Exception as ex:
|
||||
raise QAPIError(
|
||||
f"backend '{path}' cannot be instantiated: {ex}") from ex
|
||||
|
||||
if not isinstance(backend, QAPIBackend):
|
||||
raise QAPIError(
|
||||
f"backend '{path}' must be an instance of QAPIBackend")
|
||||
|
||||
return backend
|
||||
|
||||
|
||||
def main() -> int:
|
||||
@@ -75,6 +77,8 @@ def main() -> int:
|
||||
parser.add_argument('-u', '--unmask-non-abi-names', action='store_true',
|
||||
dest='unmask',
|
||||
help="expose non-ABI names in introspection")
|
||||
parser.add_argument('-B', '--backend', default=None,
|
||||
help="Python module name for code generator")
|
||||
|
||||
# Option --suppress-tracing exists so we can avoid solving build system
|
||||
# problems. TODO Drop it when we no longer need it.
|
||||
@@ -91,12 +95,14 @@ def main() -> int:
|
||||
return 1
|
||||
|
||||
try:
|
||||
generate(args.schema,
|
||||
output_dir=args.output_dir,
|
||||
prefix=args.prefix,
|
||||
unmask=args.unmask,
|
||||
builtins=args.builtins,
|
||||
gen_tracing=not args.suppress_tracing)
|
||||
schema = QAPISchema(args.schema)
|
||||
backend = create_backend(args.backend)
|
||||
backend.generate(schema,
|
||||
output_dir=args.output_dir,
|
||||
prefix=args.prefix,
|
||||
unmask=args.unmask,
|
||||
builtins=args.builtins,
|
||||
gen_tracing=not args.suppress_tracing)
|
||||
except QAPIError as err:
|
||||
print(err, file=sys.stderr)
|
||||
return 1
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
[mypy]
|
||||
strict = True
|
||||
disallow_untyped_calls = False
|
||||
python_version = 3.8
|
||||
+182
-46
@@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# QAPI schema parser
|
||||
#
|
||||
# Copyright IBM, Corp. 2011
|
||||
@@ -14,7 +12,7 @@
|
||||
# This work is licensed under the terms of the GNU GPL, version 2.
|
||||
# See the COPYING file in the top-level directory.
|
||||
|
||||
from collections import OrderedDict
|
||||
import enum
|
||||
import os
|
||||
import re
|
||||
from typing import (
|
||||
@@ -110,6 +108,11 @@ class QAPISchemaParser:
|
||||
self.exprs: List[QAPIExpression] = []
|
||||
self.docs: List[QAPIDoc] = []
|
||||
|
||||
# State for tracking qmp-example blocks and simple
|
||||
# :: literal blocks.
|
||||
self._literal_mode = False
|
||||
self._literal_mode_indent = 0
|
||||
|
||||
# Showtime!
|
||||
self._parse()
|
||||
|
||||
@@ -154,7 +157,7 @@ class QAPISchemaParser:
|
||||
"value of 'include' must be a string")
|
||||
incl_fname = os.path.join(os.path.dirname(self._fname),
|
||||
include)
|
||||
self._add_expr(OrderedDict({'include': incl_fname}), info)
|
||||
self._add_expr({'include': incl_fname}, info)
|
||||
exprs_include = self._include(include, info, incl_fname,
|
||||
self._included)
|
||||
if exprs_include:
|
||||
@@ -355,7 +358,7 @@ class QAPISchemaParser:
|
||||
raise QAPIParseError(self, "stray '%s'" % match.group(0))
|
||||
|
||||
def get_members(self) -> Dict[str, object]:
|
||||
expr: Dict[str, object] = OrderedDict()
|
||||
expr: Dict[str, object] = {}
|
||||
if self.tok == '}':
|
||||
self.accept()
|
||||
return expr
|
||||
@@ -425,12 +428,55 @@ class QAPISchemaParser:
|
||||
if self.val != '##':
|
||||
raise QAPIParseError(
|
||||
self, "junk after '##' at end of documentation comment")
|
||||
self._literal_mode = False
|
||||
return None
|
||||
if self.val == '#':
|
||||
return ''
|
||||
if self.val[1] != ' ':
|
||||
raise QAPIParseError(self, "missing space after #")
|
||||
return self.val[2:].rstrip()
|
||||
|
||||
line = self.val[2:].rstrip()
|
||||
|
||||
if re.match(r'(\.\. +qmp-example)? *::$', line):
|
||||
self._literal_mode = True
|
||||
self._literal_mode_indent = 0
|
||||
elif self._literal_mode and line:
|
||||
indent = must_match(r'\s*', line).end()
|
||||
if self._literal_mode_indent == 0:
|
||||
self._literal_mode_indent = indent
|
||||
elif indent < self._literal_mode_indent:
|
||||
# ReST directives stop at decreasing indentation
|
||||
self._literal_mode = False
|
||||
|
||||
if not self._literal_mode:
|
||||
self._validate_doc_line_format(line)
|
||||
|
||||
return line
|
||||
|
||||
def _validate_doc_line_format(self, line: str) -> None:
|
||||
"""
|
||||
Validate documentation format rules for a single line:
|
||||
1. Lines should not exceed 70 characters
|
||||
2. Sentences should be separated by two spaces
|
||||
"""
|
||||
full_line_length = len(line) + 2 # "# " = 2 characters
|
||||
if full_line_length > 70:
|
||||
# Skip URL lines - they can't be broken
|
||||
if re.match(r' *(https?|ftp)://[^ ]*$', line):
|
||||
pass
|
||||
else:
|
||||
raise QAPIParseError(
|
||||
self, "documentation line longer than 70 characters")
|
||||
|
||||
single_space_pattern = r'(\be\.g\.|^ *\d\.|([.!?])) [A-Z0-9(]'
|
||||
for m in list(re.finditer(single_space_pattern, line)):
|
||||
if not m.group(2):
|
||||
continue
|
||||
# HACK so the error message points to the offending spot
|
||||
self.pos = self.line_pos + 2 + m.start(2) + 1
|
||||
raise QAPIParseError(
|
||||
self, "Use two spaces between sentences\n"
|
||||
"If this not the end of a sentence, please report a bug.")
|
||||
|
||||
@staticmethod
|
||||
def _match_at_name_colon(string: str) -> Optional[Match[str]]:
|
||||
@@ -575,18 +621,17 @@ class QAPISchemaParser:
|
||||
)
|
||||
raise QAPIParseError(self, emsg)
|
||||
|
||||
doc.new_tagged_section(self.info, match.group(1))
|
||||
doc.new_tagged_section(
|
||||
self.info,
|
||||
QAPIDoc.Kind.from_string(match.group(1))
|
||||
)
|
||||
text = line[match.end():]
|
||||
if text:
|
||||
doc.append_line(text)
|
||||
line = self.get_doc_indented(doc)
|
||||
no_more_args = True
|
||||
elif line.startswith('='):
|
||||
raise QAPIParseError(
|
||||
self,
|
||||
"unexpected '=' markup in definition documentation")
|
||||
else:
|
||||
# tag-less paragraph
|
||||
# plain paragraph
|
||||
doc.ensure_untagged_section(self.info)
|
||||
doc.append_line(line)
|
||||
line = self.get_doc_paragraph(doc)
|
||||
@@ -594,22 +639,15 @@ class QAPISchemaParser:
|
||||
# Free-form documentation
|
||||
doc = QAPIDoc(info)
|
||||
doc.ensure_untagged_section(self.info)
|
||||
first = True
|
||||
while line is not None:
|
||||
if match := self._match_at_name_colon(line):
|
||||
raise QAPIParseError(
|
||||
self,
|
||||
"'@%s:' not allowed in free-form documentation"
|
||||
% match.group(1))
|
||||
if line.startswith('='):
|
||||
if not first:
|
||||
raise QAPIParseError(
|
||||
self,
|
||||
"'=' heading must come first in a comment block")
|
||||
doc.append_line(line)
|
||||
self.accept(False)
|
||||
line = self.get_doc_line()
|
||||
first = False
|
||||
|
||||
self.accept()
|
||||
doc.end()
|
||||
@@ -635,23 +673,51 @@ class QAPIDoc:
|
||||
Free-form documentation blocks consist only of a body section.
|
||||
"""
|
||||
|
||||
class Kind(enum.Enum):
|
||||
PLAIN = 0
|
||||
MEMBER = 1
|
||||
FEATURE = 2
|
||||
RETURNS = 3
|
||||
ERRORS = 4
|
||||
SINCE = 5
|
||||
TODO = 6
|
||||
|
||||
@staticmethod
|
||||
def from_string(kind: str) -> 'QAPIDoc.Kind':
|
||||
return QAPIDoc.Kind[kind.upper()]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name.title()
|
||||
|
||||
class Section:
|
||||
# pylint: disable=too-few-public-methods
|
||||
def __init__(self, info: QAPISourceInfo,
|
||||
tag: Optional[str] = None):
|
||||
def __init__(
|
||||
self,
|
||||
info: QAPISourceInfo,
|
||||
kind: 'QAPIDoc.Kind',
|
||||
):
|
||||
# section source info, i.e. where it begins
|
||||
self.info = info
|
||||
# section tag, if any ('Returns', '@name', ...)
|
||||
self.tag = tag
|
||||
# section kind
|
||||
self.kind = kind
|
||||
# section text without tag
|
||||
self.text = ''
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<QAPIDoc.Section kind={self.kind!r} text={self.text!r}>"
|
||||
|
||||
def append_line(self, line: str) -> None:
|
||||
self.text += line + '\n'
|
||||
|
||||
class ArgSection(Section):
|
||||
def __init__(self, info: QAPISourceInfo, tag: str):
|
||||
super().__init__(info, tag)
|
||||
def __init__(
|
||||
self,
|
||||
info: QAPISourceInfo,
|
||||
kind: 'QAPIDoc.Kind',
|
||||
name: str
|
||||
):
|
||||
super().__init__(info, kind)
|
||||
self.name = name
|
||||
self.member: Optional['QAPISchemaMember'] = None
|
||||
|
||||
def connect(self, member: 'QAPISchemaMember') -> None:
|
||||
@@ -663,7 +729,9 @@ class QAPIDoc:
|
||||
# definition doc's symbol, None for free-form doc
|
||||
self.symbol: Optional[str] = symbol
|
||||
# the sections in textual order
|
||||
self.all_sections: List[QAPIDoc.Section] = [QAPIDoc.Section(info)]
|
||||
self.all_sections: List[QAPIDoc.Section] = [
|
||||
QAPIDoc.Section(info, QAPIDoc.Kind.PLAIN)
|
||||
]
|
||||
# the body section
|
||||
self.body: Optional[QAPIDoc.Section] = self.all_sections[0]
|
||||
# dicts mapping parameter/feature names to their description
|
||||
@@ -680,55 +748,71 @@ class QAPIDoc:
|
||||
def end(self) -> None:
|
||||
for section in self.all_sections:
|
||||
section.text = section.text.strip('\n')
|
||||
if section.tag is not None and section.text == '':
|
||||
if section.kind != QAPIDoc.Kind.PLAIN and section.text == '':
|
||||
raise QAPISemError(
|
||||
section.info, "text required after '%s:'" % section.tag)
|
||||
section.info, "text required after '%s:'" % section.kind)
|
||||
|
||||
def ensure_untagged_section(self, info: QAPISourceInfo) -> None:
|
||||
if self.all_sections and not self.all_sections[-1].tag:
|
||||
kind = QAPIDoc.Kind.PLAIN
|
||||
|
||||
if self.all_sections and self.all_sections[-1].kind == kind:
|
||||
# extend current section
|
||||
self.all_sections[-1].text += '\n'
|
||||
section = self.all_sections[-1]
|
||||
if not section.text:
|
||||
# Section is empty so far; update info to start *here*.
|
||||
section.info = info
|
||||
section.text += '\n'
|
||||
return
|
||||
|
||||
# start new section
|
||||
section = self.Section(info)
|
||||
section = self.Section(info, kind)
|
||||
self.sections.append(section)
|
||||
self.all_sections.append(section)
|
||||
|
||||
def new_tagged_section(self, info: QAPISourceInfo, tag: str) -> None:
|
||||
section = self.Section(info, tag)
|
||||
if tag == 'Returns':
|
||||
def new_tagged_section(
|
||||
self,
|
||||
info: QAPISourceInfo,
|
||||
kind: 'QAPIDoc.Kind',
|
||||
) -> None:
|
||||
section = self.Section(info, kind)
|
||||
if kind == QAPIDoc.Kind.RETURNS:
|
||||
if self.returns:
|
||||
raise QAPISemError(
|
||||
info, "duplicated '%s' section" % tag)
|
||||
info, "duplicated '%s' section" % kind)
|
||||
self.returns = section
|
||||
elif tag == 'Errors':
|
||||
elif kind == QAPIDoc.Kind.ERRORS:
|
||||
if self.errors:
|
||||
raise QAPISemError(
|
||||
info, "duplicated '%s' section" % tag)
|
||||
info, "duplicated '%s' section" % kind)
|
||||
self.errors = section
|
||||
elif tag == 'Since':
|
||||
elif kind == QAPIDoc.Kind.SINCE:
|
||||
if self.since:
|
||||
raise QAPISemError(
|
||||
info, "duplicated '%s' section" % tag)
|
||||
info, "duplicated '%s' section" % kind)
|
||||
self.since = section
|
||||
self.sections.append(section)
|
||||
self.all_sections.append(section)
|
||||
|
||||
def _new_description(self, info: QAPISourceInfo, name: str,
|
||||
desc: Dict[str, ArgSection]) -> None:
|
||||
def _new_description(
|
||||
self,
|
||||
info: QAPISourceInfo,
|
||||
name: str,
|
||||
kind: 'QAPIDoc.Kind',
|
||||
desc: Dict[str, ArgSection]
|
||||
) -> None:
|
||||
if not name:
|
||||
raise QAPISemError(info, "invalid parameter name")
|
||||
if name in desc:
|
||||
raise QAPISemError(info, "'%s' parameter name duplicated" % name)
|
||||
section = self.ArgSection(info, '@' + name)
|
||||
section = self.ArgSection(info, kind, name)
|
||||
self.all_sections.append(section)
|
||||
desc[name] = section
|
||||
|
||||
def new_argument(self, info: QAPISourceInfo, name: str) -> None:
|
||||
self._new_description(info, name, self.args)
|
||||
self._new_description(info, name, QAPIDoc.Kind.MEMBER, self.args)
|
||||
|
||||
def new_feature(self, info: QAPISourceInfo, name: str) -> None:
|
||||
self._new_description(info, name, self.features)
|
||||
self._new_description(info, name, QAPIDoc.Kind.FEATURE, self.features)
|
||||
|
||||
def append_line(self, line: str) -> None:
|
||||
self.all_sections[-1].append_line(line)
|
||||
@@ -740,8 +824,23 @@ class QAPIDoc:
|
||||
raise QAPISemError(member.info,
|
||||
"%s '%s' lacks documentation"
|
||||
% (member.role, member.name))
|
||||
self.args[member.name] = QAPIDoc.ArgSection(
|
||||
self.info, '@' + member.name)
|
||||
# Insert stub documentation section for missing member docs.
|
||||
# TODO: drop when undocumented members are outlawed
|
||||
|
||||
section = QAPIDoc.ArgSection(
|
||||
self.info, QAPIDoc.Kind.MEMBER, member.name)
|
||||
self.args[member.name] = section
|
||||
|
||||
# Determine where to insert stub doc - it should go at the
|
||||
# end of the members section(s), if any. Note that index 0
|
||||
# is assumed to be an untagged intro section, even if it is
|
||||
# empty.
|
||||
index = 1
|
||||
if len(self.all_sections) > 1:
|
||||
while self.all_sections[index].kind == QAPIDoc.Kind.MEMBER:
|
||||
index += 1
|
||||
self.all_sections.insert(index, section)
|
||||
|
||||
self.args[member.name].connect(member)
|
||||
|
||||
def connect_feature(self, feature: 'QAPISchemaFeature') -> None:
|
||||
@@ -751,6 +850,43 @@ class QAPIDoc:
|
||||
% feature.name)
|
||||
self.features[feature.name].connect(feature)
|
||||
|
||||
def ensure_returns(self, info: QAPISourceInfo) -> None:
|
||||
|
||||
def _insert_near_kind(
|
||||
kind: QAPIDoc.Kind,
|
||||
new_sect: QAPIDoc.Section,
|
||||
after: bool = False,
|
||||
) -> bool:
|
||||
for idx, sect in enumerate(reversed(self.all_sections)):
|
||||
if sect.kind == kind:
|
||||
pos = len(self.all_sections) - idx - 1
|
||||
if after:
|
||||
pos += 1
|
||||
self.all_sections.insert(pos, new_sect)
|
||||
return True
|
||||
return False
|
||||
|
||||
if any(s.kind == QAPIDoc.Kind.RETURNS for s in self.all_sections):
|
||||
return
|
||||
|
||||
# Stub "Returns" section for undocumented returns value
|
||||
stub = QAPIDoc.Section(info, QAPIDoc.Kind.RETURNS)
|
||||
|
||||
if any(_insert_near_kind(kind, stub, after) for kind, after in (
|
||||
# 1. If arguments, right after those.
|
||||
(QAPIDoc.Kind.MEMBER, True),
|
||||
# 2. Elif errors, right *before* those.
|
||||
(QAPIDoc.Kind.ERRORS, False),
|
||||
# 3. Elif features, right *before* those.
|
||||
(QAPIDoc.Kind.FEATURE, False),
|
||||
)):
|
||||
return
|
||||
|
||||
# Otherwise, it should go right after the intro. The intro
|
||||
# is always the first section and is always present (even
|
||||
# when empty), so we can insert directly at index=1 blindly.
|
||||
self.all_sections.insert(1, stub)
|
||||
|
||||
def check_expr(self, expr: QAPIExpression) -> None:
|
||||
if 'command' in expr:
|
||||
if self.returns and 'returns' not in expr:
|
||||
|
||||
@@ -17,7 +17,9 @@ disable=consider-using-f-string,
|
||||
too-many-arguments,
|
||||
too-many-branches,
|
||||
too-many-instance-attributes,
|
||||
too-many-positional-arguments,
|
||||
too-many-statements,
|
||||
unknown-option-value,
|
||||
useless-option-value,
|
||||
|
||||
[REPORTS]
|
||||
|
||||
+37
-8
@@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# QAPI schema internal representation
|
||||
#
|
||||
# Copyright (c) 2015-2019 Red Hat Inc.
|
||||
@@ -19,7 +17,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from collections import OrderedDict
|
||||
import os
|
||||
import re
|
||||
from typing import (
|
||||
@@ -29,6 +26,7 @@ from typing import (
|
||||
List,
|
||||
Optional,
|
||||
Union,
|
||||
ValuesView,
|
||||
cast,
|
||||
)
|
||||
|
||||
@@ -556,7 +554,7 @@ class QAPISchemaObjectType(QAPISchemaType):
|
||||
super().check(schema)
|
||||
assert self._checked and not self._check_complete
|
||||
|
||||
seen = OrderedDict()
|
||||
seen = {}
|
||||
if self._base_name:
|
||||
self.base = schema.resolve_type(self._base_name, self.info,
|
||||
"'base'")
|
||||
@@ -933,8 +931,11 @@ class QAPISchemaEnumMember(QAPISchemaMember):
|
||||
class QAPISchemaFeature(QAPISchemaMember):
|
||||
role = 'feature'
|
||||
|
||||
# Features which are standardized across all schemas
|
||||
SPECIAL_NAMES = ['deprecated', 'unstable']
|
||||
|
||||
def is_special(self) -> bool:
|
||||
return self.name in ('deprecated', 'unstable')
|
||||
return self.name in QAPISchemaFeature.SPECIAL_NAMES
|
||||
|
||||
|
||||
class QAPISchemaObjectTypeMember(QAPISchemaMember):
|
||||
@@ -1059,6 +1060,9 @@ class QAPISchemaCommand(QAPISchemaDefinition):
|
||||
if self.arg_type and self.arg_type.is_implicit():
|
||||
self.arg_type.connect_doc(doc)
|
||||
|
||||
if self.ret_type and self.info:
|
||||
doc.ensure_returns(self.info)
|
||||
|
||||
def visit(self, visitor: QAPISchemaVisitor) -> None:
|
||||
super().visit(visitor)
|
||||
visitor.visit_command(
|
||||
@@ -1137,7 +1141,17 @@ class QAPISchema:
|
||||
self.docs = parser.docs
|
||||
self._entity_list: List[QAPISchemaEntity] = []
|
||||
self._entity_dict: Dict[str, QAPISchemaDefinition] = {}
|
||||
self._module_dict: Dict[str, QAPISchemaModule] = OrderedDict()
|
||||
self._module_dict: Dict[str, QAPISchemaModule] = {}
|
||||
# NB, values in the dict will identify the first encountered
|
||||
# usage of a named feature only
|
||||
self._feature_dict: Dict[str, QAPISchemaFeature] = {}
|
||||
|
||||
# All schemas get the names defined in the QapiSpecialFeature enum.
|
||||
# Rely on dict iteration order matching insertion order so that
|
||||
# the special names are emitted first when generating code.
|
||||
for f in QAPISchemaFeature.SPECIAL_NAMES:
|
||||
self._feature_dict[f] = QAPISchemaFeature(f, None)
|
||||
|
||||
self._schema_dir = os.path.dirname(fname)
|
||||
self._make_module(QAPISchemaModule.BUILTIN_MODULE_NAME)
|
||||
self._make_module(fname)
|
||||
@@ -1147,6 +1161,9 @@ class QAPISchema:
|
||||
self._def_exprs(exprs)
|
||||
self.check()
|
||||
|
||||
def features(self) -> ValuesView[QAPISchemaFeature]:
|
||||
return self._feature_dict.values()
|
||||
|
||||
def _def_entity(self, ent: QAPISchemaEntity) -> None:
|
||||
self._entity_list.append(ent)
|
||||
|
||||
@@ -1258,6 +1275,12 @@ class QAPISchema:
|
||||
) -> List[QAPISchemaFeature]:
|
||||
if features is None:
|
||||
return []
|
||||
|
||||
for f in features:
|
||||
feat = QAPISchemaFeature(f['name'], info)
|
||||
if feat.name not in self._feature_dict:
|
||||
self._feature_dict[feat.name] = feat
|
||||
|
||||
return [QAPISchemaFeature(f['name'], info,
|
||||
QAPISchemaIfCond(f.get('if')))
|
||||
for f in features]
|
||||
@@ -1431,7 +1454,7 @@ class QAPISchema:
|
||||
ifcond = QAPISchemaIfCond(expr.get('if'))
|
||||
info = expr.info
|
||||
features = self._make_features(expr.get('features'), info)
|
||||
if isinstance(data, OrderedDict):
|
||||
if isinstance(data, dict):
|
||||
data = self._make_implicit_object_type(
|
||||
name, info, ifcond,
|
||||
'arg', self._make_members(data, info))
|
||||
@@ -1450,7 +1473,7 @@ class QAPISchema:
|
||||
ifcond = QAPISchemaIfCond(expr.get('if'))
|
||||
info = expr.info
|
||||
features = self._make_features(expr.get('features'), info)
|
||||
if isinstance(data, OrderedDict):
|
||||
if isinstance(data, dict):
|
||||
data = self._make_implicit_object_type(
|
||||
name, info, ifcond,
|
||||
'arg', self._make_members(data, info))
|
||||
@@ -1485,6 +1508,12 @@ class QAPISchema:
|
||||
for doc in self.docs:
|
||||
doc.check()
|
||||
|
||||
features = list(self._feature_dict.values())
|
||||
if len(features) > 64:
|
||||
raise QAPISemError(
|
||||
features[64].info,
|
||||
"Maximum of 64 schema features is permitted")
|
||||
|
||||
def visit(self, visitor: QAPISchemaVisitor) -> None:
|
||||
visitor.visit_begin(self)
|
||||
for mod in self._module_dict.values():
|
||||
|
||||
@@ -47,9 +47,9 @@ class QAPISourceInfo:
|
||||
self.defn_meta = meta
|
||||
self.defn_name = name
|
||||
|
||||
def next_line(self: T) -> T:
|
||||
def next_line(self: T, n: int = 1) -> T:
|
||||
info = copy.copy(self)
|
||||
info.line += 1
|
||||
info.line += n
|
||||
return info
|
||||
|
||||
def loc(self) -> str:
|
||||
|
||||
+11
-12
@@ -16,11 +16,7 @@ This work is licensed under the terms of the GNU GPL, version 2.
|
||||
from typing import List, Optional
|
||||
|
||||
from .common import c_enum_const, c_name, mcgen
|
||||
from .gen import (
|
||||
QAPISchemaModularCVisitor,
|
||||
gen_special_features,
|
||||
ifcontext,
|
||||
)
|
||||
from .gen import QAPISchemaModularCVisitor, gen_features, ifcontext
|
||||
from .schema import (
|
||||
QAPISchema,
|
||||
QAPISchemaAlternatives,
|
||||
@@ -61,17 +57,17 @@ const QEnumLookup %(c_name)s_lookup = {
|
||||
index=index, name=memb.name)
|
||||
ret += memb.ifcond.gen_endif()
|
||||
|
||||
special_features = gen_special_features(memb.features)
|
||||
if special_features != '0':
|
||||
features = gen_features(memb.features)
|
||||
if features != '0':
|
||||
feats += mcgen('''
|
||||
[%(index)s] = %(special_features)s,
|
||||
[%(index)s] = %(features)s,
|
||||
''',
|
||||
index=index, special_features=special_features)
|
||||
index=index, features=features)
|
||||
|
||||
if feats:
|
||||
ret += mcgen('''
|
||||
},
|
||||
.special_features = (const unsigned char[%(max_index)s]) {
|
||||
.features = (const uint64_t[%(max_index)s]) {
|
||||
''',
|
||||
max_index=max_index)
|
||||
ret += feats
|
||||
@@ -308,11 +304,14 @@ class QAPISchemaGenTypeVisitor(QAPISchemaModularCVisitor):
|
||||
#include "qapi/dealloc-visitor.h"
|
||||
#include "%(types)s.h"
|
||||
#include "%(visit)s.h"
|
||||
#include "%(prefix)sqapi-features.h"
|
||||
''',
|
||||
types=types, visit=visit))
|
||||
types=types, visit=visit,
|
||||
prefix=self._prefix))
|
||||
self._genh.preamble_add(mcgen('''
|
||||
#include "qapi/qapi-builtin-types.h"
|
||||
'''))
|
||||
''',
|
||||
prefix=self._prefix))
|
||||
|
||||
def visit_begin(self, schema: QAPISchema) -> None:
|
||||
# gen_object() is recursive, ensure it doesn't visit the empty type
|
||||
|
||||
+9
-12
@@ -21,11 +21,7 @@ from .common import (
|
||||
indent,
|
||||
mcgen,
|
||||
)
|
||||
from .gen import (
|
||||
QAPISchemaModularCVisitor,
|
||||
gen_special_features,
|
||||
ifcontext,
|
||||
)
|
||||
from .gen import QAPISchemaModularCVisitor, gen_features, ifcontext
|
||||
from .schema import (
|
||||
QAPISchema,
|
||||
QAPISchemaAlternatives,
|
||||
@@ -103,15 +99,15 @@ bool visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp)
|
||||
''',
|
||||
name=memb.name, has=has)
|
||||
indent.increase()
|
||||
special_features = gen_special_features(memb.features)
|
||||
if special_features != '0':
|
||||
features = gen_features(memb.features)
|
||||
if features != '0':
|
||||
ret += mcgen('''
|
||||
if (visit_policy_reject(v, "%(name)s", %(special_features)s, errp)) {
|
||||
if (visit_policy_reject(v, "%(name)s", %(features)s, errp)) {
|
||||
return false;
|
||||
}
|
||||
if (!visit_policy_skip(v, "%(name)s", %(special_features)s)) {
|
||||
if (!visit_policy_skip(v, "%(name)s", %(features)s)) {
|
||||
''',
|
||||
name=memb.name, special_features=special_features)
|
||||
name=memb.name, features=features)
|
||||
indent.increase()
|
||||
ret += mcgen('''
|
||||
if (!visit_type_%(c_type)s(v, "%(name)s", &obj->%(c_name)s, errp)) {
|
||||
@@ -120,7 +116,7 @@ bool visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp)
|
||||
''',
|
||||
c_type=memb.type.c_name(), name=memb.name,
|
||||
c_name=c_name(memb.name))
|
||||
if special_features != '0':
|
||||
if features != '0':
|
||||
indent.decrease()
|
||||
ret += mcgen('''
|
||||
}
|
||||
@@ -360,8 +356,9 @@ class QAPISchemaGenVisitVisitor(QAPISchemaModularCVisitor):
|
||||
#include "qemu/osdep.h"
|
||||
#include "qapi/error.h"
|
||||
#include "%(visit)s.h"
|
||||
#include "%(prefix)sqapi-features.h"
|
||||
''',
|
||||
visit=visit))
|
||||
visit=visit, prefix=self._prefix))
|
||||
self._genh.preamble_add(mcgen('''
|
||||
#include "qapi/qapi-builtin-visit.h"
|
||||
#include "%(types)s.h"
|
||||
|
||||
Executable
+449
@@ -0,0 +1,449 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# This tool reads a disk image in any format and converts it to qcow2,
|
||||
# writing the result directly to stdout.
|
||||
#
|
||||
# Copyright (C) 2024 Igalia, S.L.
|
||||
#
|
||||
# Authors: Alberto Garcia <berto@igalia.com>
|
||||
# Madeeha Javed <javed@igalia.com>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#
|
||||
# qcow2 files produced by this script are always arranged like this:
|
||||
#
|
||||
# - qcow2 header
|
||||
# - refcount table
|
||||
# - refcount blocks
|
||||
# - L1 table
|
||||
# - L2 tables
|
||||
# - Data clusters
|
||||
#
|
||||
# A note about variable names: in qcow2 there is one refcount table
|
||||
# and one (active) L1 table, although each can occupy several
|
||||
# clusters. For the sake of simplicity the code sometimes talks about
|
||||
# refcount tables and L1 tables when referring to those clusters.
|
||||
|
||||
import argparse
|
||||
import errno
|
||||
import math
|
||||
import os
|
||||
import signal
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from contextlib import contextmanager
|
||||
|
||||
QCOW2_DEFAULT_CLUSTER_SIZE = 65536
|
||||
QCOW2_DEFAULT_REFCOUNT_BITS = 16
|
||||
QCOW2_FEATURE_NAME_TABLE = 0x6803F857
|
||||
QCOW2_DATA_FILE_NAME_STRING = 0x44415441
|
||||
QCOW2_V3_HEADER_LENGTH = 112 # Header length in QEMU 9.0. Must be a multiple of 8
|
||||
QCOW2_INCOMPAT_DATA_FILE_BIT = 2
|
||||
QCOW2_AUTOCLEAR_DATA_FILE_RAW_BIT = 1
|
||||
QCOW_OFLAG_COPIED = 1 << 63
|
||||
QEMU_STORAGE_DAEMON = "qemu-storage-daemon"
|
||||
|
||||
|
||||
def bitmap_set(bitmap, idx):
|
||||
bitmap[idx // 8] |= 1 << (idx % 8)
|
||||
|
||||
|
||||
def bitmap_is_set(bitmap, idx):
|
||||
return (bitmap[idx // 8] & (1 << (idx % 8))) != 0
|
||||
|
||||
|
||||
def bitmap_iterator(bitmap, length):
|
||||
for idx in range(length):
|
||||
if bitmap_is_set(bitmap, idx):
|
||||
yield idx
|
||||
|
||||
|
||||
def align_up(num, d):
|
||||
return d * math.ceil(num / d)
|
||||
|
||||
|
||||
# Holes in the input file contain only zeroes so we can skip them and
|
||||
# save time. This function returns the indexes of the clusters that
|
||||
# are known to contain data. Those are the ones that we need to read.
|
||||
def clusters_with_data(fd, cluster_size):
|
||||
data_to = 0
|
||||
while True:
|
||||
try:
|
||||
data_from = os.lseek(fd, data_to, os.SEEK_DATA)
|
||||
data_to = align_up(os.lseek(fd, data_from, os.SEEK_HOLE), cluster_size)
|
||||
for idx in range(data_from // cluster_size, data_to // cluster_size):
|
||||
yield idx
|
||||
except OSError as err:
|
||||
if err.errno == errno.ENXIO: # End of file reached
|
||||
break
|
||||
raise err
|
||||
|
||||
|
||||
# write_qcow2_content() expects a raw input file. If we have a different
|
||||
# format we can use qemu-storage-daemon to make it appear as raw.
|
||||
@contextmanager
|
||||
def get_input_as_raw_file(input_file, input_format):
|
||||
if input_format == "raw":
|
||||
yield input_file
|
||||
return
|
||||
try:
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
pid_file = os.path.join(temp_dir, "pid")
|
||||
raw_file = os.path.join(temp_dir, "raw")
|
||||
open(raw_file, "wb").close()
|
||||
ret = subprocess.run(
|
||||
[
|
||||
QEMU_STORAGE_DAEMON,
|
||||
"--daemonize",
|
||||
"--pidfile", pid_file,
|
||||
"--blockdev", f"driver=file,node-name=file0,driver=file,filename={input_file},read-only=on",
|
||||
"--blockdev", f"driver={input_format},node-name=disk0,file=file0,read-only=on",
|
||||
"--export", f"type=fuse,id=export0,node-name=disk0,mountpoint={raw_file},writable=off",
|
||||
],
|
||||
capture_output=True,
|
||||
)
|
||||
if ret.returncode != 0:
|
||||
sys.exit("[Error] Could not start the qemu-storage-daemon:\n" +
|
||||
ret.stderr.decode().rstrip('\n'))
|
||||
yield raw_file
|
||||
finally:
|
||||
# Kill the storage daemon on exit
|
||||
# and remove all temporary files
|
||||
if os.path.exists(pid_file):
|
||||
with open(pid_file, "r") as f:
|
||||
pid = int(f.readline())
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
while os.path.exists(pid_file):
|
||||
time.sleep(0.1)
|
||||
os.unlink(raw_file)
|
||||
os.rmdir(temp_dir)
|
||||
|
||||
|
||||
def write_features(cluster, offset, data_file_name):
|
||||
if data_file_name is not None:
|
||||
encoded_name = data_file_name.encode("utf-8")
|
||||
padded_name_len = align_up(len(encoded_name), 8)
|
||||
struct.pack_into(f">II{padded_name_len}s", cluster, offset,
|
||||
QCOW2_DATA_FILE_NAME_STRING,
|
||||
len(encoded_name),
|
||||
encoded_name)
|
||||
offset += 8 + padded_name_len
|
||||
|
||||
qcow2_features = [
|
||||
# Incompatible
|
||||
(0, 0, "dirty bit"),
|
||||
(0, 1, "corrupt bit"),
|
||||
(0, 2, "external data file"),
|
||||
(0, 3, "compression type"),
|
||||
(0, 4, "extended L2 entries"),
|
||||
# Compatible
|
||||
(1, 0, "lazy refcounts"),
|
||||
# Autoclear
|
||||
(2, 0, "bitmaps"),
|
||||
(2, 1, "raw external data"),
|
||||
]
|
||||
struct.pack_into(">I", cluster, offset, QCOW2_FEATURE_NAME_TABLE)
|
||||
struct.pack_into(">I", cluster, offset + 4, len(qcow2_features) * 48)
|
||||
offset += 8
|
||||
for feature_type, feature_bit, feature_name in qcow2_features:
|
||||
struct.pack_into(">BB46s", cluster, offset,
|
||||
feature_type, feature_bit, feature_name.encode("ascii"))
|
||||
offset += 48
|
||||
|
||||
|
||||
def write_qcow2_content(input_file, cluster_size, refcount_bits, data_file_name, data_file_raw):
|
||||
# Some basic values
|
||||
l1_entries_per_table = cluster_size // 8
|
||||
l2_entries_per_table = cluster_size // 8
|
||||
refcounts_per_table = cluster_size // 8
|
||||
refcounts_per_block = cluster_size * 8 // refcount_bits
|
||||
|
||||
# Virtual disk size, number of data clusters and L1 entries
|
||||
disk_size = align_up(os.path.getsize(input_file), 512)
|
||||
total_data_clusters = math.ceil(disk_size / cluster_size)
|
||||
l1_entries = math.ceil(total_data_clusters / l2_entries_per_table)
|
||||
allocated_l1_tables = math.ceil(l1_entries / l1_entries_per_table)
|
||||
|
||||
# Max L1 table size is 32 MB (QCOW_MAX_L1_SIZE in block/qcow2.h)
|
||||
if (l1_entries * 8) > (32 * 1024 * 1024):
|
||||
sys.exit("[Error] The image size is too large. Try using a larger cluster size.")
|
||||
|
||||
# Two bitmaps indicating which L1 and L2 entries are set
|
||||
l1_bitmap = bytearray(allocated_l1_tables * l1_entries_per_table // 8)
|
||||
l2_bitmap = bytearray(l1_entries * l2_entries_per_table // 8)
|
||||
allocated_l2_tables = 0
|
||||
allocated_data_clusters = 0
|
||||
|
||||
if data_file_raw:
|
||||
# If data_file_raw is set then all clusters are allocated and
|
||||
# we don't need to read the input file at all.
|
||||
allocated_l2_tables = l1_entries
|
||||
for idx in range(l1_entries):
|
||||
bitmap_set(l1_bitmap, idx)
|
||||
for idx in range(total_data_clusters):
|
||||
bitmap_set(l2_bitmap, idx)
|
||||
else:
|
||||
# Open the input file for reading
|
||||
fd = os.open(input_file, os.O_RDONLY)
|
||||
zero_cluster = bytes(cluster_size)
|
||||
# Read all the clusters that contain data
|
||||
for idx in clusters_with_data(fd, cluster_size):
|
||||
cluster = os.pread(fd, cluster_size, cluster_size * idx)
|
||||
# If the last cluster is smaller than cluster_size pad it with zeroes
|
||||
if len(cluster) < cluster_size:
|
||||
cluster += bytes(cluster_size - len(cluster))
|
||||
# If a cluster has non-zero data then it must be allocated
|
||||
# in the output file and its L2 entry must be set
|
||||
if cluster != zero_cluster:
|
||||
bitmap_set(l2_bitmap, idx)
|
||||
allocated_data_clusters += 1
|
||||
# Allocated data clusters also need their corresponding L1 entry and L2 table
|
||||
l1_idx = math.floor(idx / l2_entries_per_table)
|
||||
if not bitmap_is_set(l1_bitmap, l1_idx):
|
||||
bitmap_set(l1_bitmap, l1_idx)
|
||||
allocated_l2_tables += 1
|
||||
|
||||
# Total amount of allocated clusters excluding the refcount blocks and table
|
||||
total_allocated_clusters = 1 + allocated_l1_tables + allocated_l2_tables
|
||||
if data_file_name is None:
|
||||
total_allocated_clusters += allocated_data_clusters
|
||||
|
||||
# Clusters allocated for the refcount blocks and table
|
||||
allocated_refcount_blocks = math.ceil(total_allocated_clusters / refcounts_per_block)
|
||||
allocated_refcount_tables = math.ceil(allocated_refcount_blocks / refcounts_per_table)
|
||||
|
||||
# Now we have a problem because allocated_refcount_blocks and allocated_refcount_tables...
|
||||
# (a) increase total_allocated_clusters, and
|
||||
# (b) need to be recalculated when total_allocated_clusters is increased
|
||||
# So we need to repeat the calculation as long as the numbers change
|
||||
while True:
|
||||
new_total_allocated_clusters = total_allocated_clusters + allocated_refcount_tables + allocated_refcount_blocks
|
||||
new_allocated_refcount_blocks = math.ceil(new_total_allocated_clusters / refcounts_per_block)
|
||||
if new_allocated_refcount_blocks > allocated_refcount_blocks:
|
||||
allocated_refcount_blocks = new_allocated_refcount_blocks
|
||||
allocated_refcount_tables = math.ceil(allocated_refcount_blocks / refcounts_per_table)
|
||||
else:
|
||||
break
|
||||
|
||||
# Now that we have the final numbers we can update total_allocated_clusters
|
||||
total_allocated_clusters += allocated_refcount_tables + allocated_refcount_blocks
|
||||
|
||||
# At this point we have the exact number of clusters that the output
|
||||
# image is going to use so we can calculate all the offsets.
|
||||
current_cluster_idx = 1
|
||||
|
||||
refcount_table_offset = current_cluster_idx * cluster_size
|
||||
current_cluster_idx += allocated_refcount_tables
|
||||
|
||||
refcount_block_offset = current_cluster_idx * cluster_size
|
||||
current_cluster_idx += allocated_refcount_blocks
|
||||
|
||||
l1_table_offset = current_cluster_idx * cluster_size
|
||||
current_cluster_idx += allocated_l1_tables
|
||||
|
||||
l2_table_offset = current_cluster_idx * cluster_size
|
||||
current_cluster_idx += allocated_l2_tables
|
||||
|
||||
data_clusters_offset = current_cluster_idx * cluster_size
|
||||
|
||||
# Calculate some values used in the qcow2 header
|
||||
if allocated_l1_tables == 0:
|
||||
l1_table_offset = 0
|
||||
|
||||
hdr_cluster_bits = int(math.log2(cluster_size))
|
||||
hdr_refcount_bits = int(math.log2(refcount_bits))
|
||||
hdr_length = QCOW2_V3_HEADER_LENGTH
|
||||
hdr_incompat_features = 0
|
||||
if data_file_name is not None:
|
||||
hdr_incompat_features |= 1 << QCOW2_INCOMPAT_DATA_FILE_BIT
|
||||
hdr_autoclear_features = 0
|
||||
if data_file_raw:
|
||||
hdr_autoclear_features |= 1 << QCOW2_AUTOCLEAR_DATA_FILE_RAW_BIT
|
||||
|
||||
### Write qcow2 header
|
||||
cluster = bytearray(cluster_size)
|
||||
struct.pack_into(">4sIQIIQIIQQIIQQQQII", cluster, 0,
|
||||
b"QFI\xfb", # QCOW magic string
|
||||
3, # version
|
||||
0, # backing file offset
|
||||
0, # backing file sizes
|
||||
hdr_cluster_bits,
|
||||
disk_size,
|
||||
0, # encryption method
|
||||
l1_entries,
|
||||
l1_table_offset,
|
||||
refcount_table_offset,
|
||||
allocated_refcount_tables,
|
||||
0, # number of snapshots
|
||||
0, # snapshot table offset
|
||||
hdr_incompat_features,
|
||||
0, # compatible features
|
||||
hdr_autoclear_features,
|
||||
hdr_refcount_bits,
|
||||
hdr_length,
|
||||
)
|
||||
|
||||
write_features(cluster, hdr_length, data_file_name)
|
||||
|
||||
sys.stdout.buffer.write(cluster)
|
||||
|
||||
### Write refcount table
|
||||
cur_offset = refcount_block_offset
|
||||
remaining_refcount_table_entries = allocated_refcount_blocks # Each entry is a pointer to a refcount block
|
||||
while remaining_refcount_table_entries > 0:
|
||||
cluster = bytearray(cluster_size)
|
||||
to_write = min(remaining_refcount_table_entries, refcounts_per_table)
|
||||
remaining_refcount_table_entries -= to_write
|
||||
for idx in range(to_write):
|
||||
struct.pack_into(">Q", cluster, idx * 8, cur_offset)
|
||||
cur_offset += cluster_size
|
||||
sys.stdout.buffer.write(cluster)
|
||||
|
||||
### Write refcount blocks
|
||||
remaining_refcount_block_entries = total_allocated_clusters # One entry for each allocated cluster
|
||||
for tbl in range(allocated_refcount_blocks):
|
||||
cluster = bytearray(cluster_size)
|
||||
to_write = min(remaining_refcount_block_entries, refcounts_per_block)
|
||||
remaining_refcount_block_entries -= to_write
|
||||
# All refcount entries contain the number 1. The only difference
|
||||
# is their bit width, defined when the image is created.
|
||||
for idx in range(to_write):
|
||||
if refcount_bits == 64:
|
||||
struct.pack_into(">Q", cluster, idx * 8, 1)
|
||||
elif refcount_bits == 32:
|
||||
struct.pack_into(">L", cluster, idx * 4, 1)
|
||||
elif refcount_bits == 16:
|
||||
struct.pack_into(">H", cluster, idx * 2, 1)
|
||||
elif refcount_bits == 8:
|
||||
cluster[idx] = 1
|
||||
elif refcount_bits == 4:
|
||||
cluster[idx // 2] |= 1 << ((idx % 2) * 4)
|
||||
elif refcount_bits == 2:
|
||||
cluster[idx // 4] |= 1 << ((idx % 4) * 2)
|
||||
elif refcount_bits == 1:
|
||||
cluster[idx // 8] |= 1 << (idx % 8)
|
||||
sys.stdout.buffer.write(cluster)
|
||||
|
||||
### Write L1 table
|
||||
cur_offset = l2_table_offset
|
||||
for tbl in range(allocated_l1_tables):
|
||||
cluster = bytearray(cluster_size)
|
||||
for idx in range(l1_entries_per_table):
|
||||
l1_idx = tbl * l1_entries_per_table + idx
|
||||
if bitmap_is_set(l1_bitmap, l1_idx):
|
||||
struct.pack_into(">Q", cluster, idx * 8, cur_offset | QCOW_OFLAG_COPIED)
|
||||
cur_offset += cluster_size
|
||||
sys.stdout.buffer.write(cluster)
|
||||
|
||||
### Write L2 tables
|
||||
cur_offset = data_clusters_offset
|
||||
for tbl in range(l1_entries):
|
||||
# Skip the empty L2 tables. We can identify them because
|
||||
# there is no L1 entry pointing at them.
|
||||
if bitmap_is_set(l1_bitmap, tbl):
|
||||
cluster = bytearray(cluster_size)
|
||||
for idx in range(l2_entries_per_table):
|
||||
l2_idx = tbl * l2_entries_per_table + idx
|
||||
if bitmap_is_set(l2_bitmap, l2_idx):
|
||||
if data_file_name is None:
|
||||
struct.pack_into(">Q", cluster, idx * 8, cur_offset | QCOW_OFLAG_COPIED)
|
||||
cur_offset += cluster_size
|
||||
else:
|
||||
struct.pack_into(">Q", cluster, idx * 8, (l2_idx * cluster_size) | QCOW_OFLAG_COPIED)
|
||||
sys.stdout.buffer.write(cluster)
|
||||
|
||||
### Write data clusters
|
||||
if data_file_name is None:
|
||||
for idx in bitmap_iterator(l2_bitmap, total_data_clusters):
|
||||
cluster = os.pread(fd, cluster_size, cluster_size * idx)
|
||||
# If the last cluster is smaller than cluster_size pad it with zeroes
|
||||
if len(cluster) < cluster_size:
|
||||
cluster += bytes(cluster_size - len(cluster))
|
||||
sys.stdout.buffer.write(cluster)
|
||||
|
||||
if not data_file_raw:
|
||||
os.close(fd)
|
||||
|
||||
|
||||
def main():
|
||||
# Command-line arguments
|
||||
parser = argparse.ArgumentParser(
|
||||
description="This program converts a QEMU disk image to qcow2 "
|
||||
"and writes it to the standard output"
|
||||
)
|
||||
parser.add_argument("input_file", help="name of the input file")
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
dest="input_format",
|
||||
metavar="input_format",
|
||||
help="format of the input file (default: raw)",
|
||||
default="raw",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
dest="cluster_size",
|
||||
metavar="cluster_size",
|
||||
help=f"qcow2 cluster size (default: {QCOW2_DEFAULT_CLUSTER_SIZE})",
|
||||
default=QCOW2_DEFAULT_CLUSTER_SIZE,
|
||||
type=int,
|
||||
choices=[1 << x for x in range(9, 22)],
|
||||
)
|
||||
parser.add_argument(
|
||||
"-r",
|
||||
dest="refcount_bits",
|
||||
metavar="refcount_bits",
|
||||
help=f"width of the reference count entries (default: {QCOW2_DEFAULT_REFCOUNT_BITS})",
|
||||
default=QCOW2_DEFAULT_REFCOUNT_BITS,
|
||||
type=int,
|
||||
choices=[1 << x for x in range(7)],
|
||||
)
|
||||
parser.add_argument(
|
||||
"-d",
|
||||
dest="data_file",
|
||||
help="create an image with input_file as an external data file",
|
||||
action="store_true",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-R",
|
||||
dest="data_file_raw",
|
||||
help="enable data_file_raw on the generated image (implies -d)",
|
||||
action="store_true",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.data_file_raw:
|
||||
args.data_file = True
|
||||
|
||||
if not os.path.isfile(args.input_file):
|
||||
sys.exit(f"[Error] {args.input_file} does not exist or is not a regular file.")
|
||||
|
||||
if args.data_file and args.input_format != "raw":
|
||||
sys.exit("[Error] External data files can only be used with raw input images")
|
||||
|
||||
# A 512 byte header is too small for the data file name extension
|
||||
if args.data_file and args.cluster_size == 512:
|
||||
sys.exit("[Error] External data files require a larger cluster size")
|
||||
|
||||
if sys.stdout.isatty():
|
||||
sys.exit("[Error] Refusing to write to a tty. Try redirecting stdout.")
|
||||
|
||||
if args.data_file:
|
||||
data_file_name = args.input_file
|
||||
else:
|
||||
data_file_name = None
|
||||
|
||||
with get_input_as_raw_file(args.input_file, args.input_format) as raw_file:
|
||||
write_qcow2_content(
|
||||
raw_file,
|
||||
args.cluster_size,
|
||||
args.refcount_bits,
|
||||
data_file_name,
|
||||
args.data_file_raw,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+50
-28
@@ -144,35 +144,35 @@ loongarch64_magic='\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x
|
||||
loongarch64_mask='\xff\xff\xff\xff\xff\xff\xff\xfc\x00\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'
|
||||
loongarch64_family=loongarch
|
||||
|
||||
qemu_get_family() {
|
||||
cpu=${HOST_ARCH:-$(uname -m)}
|
||||
# Converts the name of a host CPU architecture to the corresponding QEMU
|
||||
# target.
|
||||
#
|
||||
# FIXME: This can probably be simplified a lot by dropping most entries.
|
||||
# Remember that the script is only used on Linux, so we only need to
|
||||
# handle the strings Linux uses to report the host CPU architecture.
|
||||
qemu_normalize() {
|
||||
cpu="$1"
|
||||
case "$cpu" in
|
||||
amd64|i386|i486|i586|i686|i86pc|BePC|x86_64)
|
||||
i[3-6]86)
|
||||
echo "i386"
|
||||
;;
|
||||
mips*)
|
||||
echo "mips"
|
||||
amd64)
|
||||
echo "x86_64"
|
||||
;;
|
||||
"Power Macintosh"|ppc64|powerpc|ppc)
|
||||
powerpc)
|
||||
echo "ppc"
|
||||
;;
|
||||
ppc64el|ppc64le)
|
||||
echo "ppcle"
|
||||
ppc64el)
|
||||
echo "ppc64le"
|
||||
;;
|
||||
arm|armel|armhf|arm64|armv[4-9]*l|aarch64)
|
||||
armel|armhf|armv[4-9]*l)
|
||||
echo "arm"
|
||||
;;
|
||||
armeb|armv[4-9]*b|aarch64_be)
|
||||
armv[4-9]*b)
|
||||
echo "armeb"
|
||||
;;
|
||||
sparc*)
|
||||
echo "sparc"
|
||||
;;
|
||||
riscv*)
|
||||
echo "riscv"
|
||||
;;
|
||||
loongarch*)
|
||||
echo "loongarch"
|
||||
arm64)
|
||||
echo "aarch64"
|
||||
;;
|
||||
*)
|
||||
echo "$cpu"
|
||||
@@ -205,6 +205,9 @@ Usage: qemu-binfmt-conf.sh [--qemu-path PATH][--debian][--systemd CPU]
|
||||
--persistent: if yes, the interpreter is loaded when binfmt is
|
||||
configured and remains in memory. All future uses
|
||||
are cloned from the open file.
|
||||
--ignore-family: if yes, it is assumed that the host CPU (e.g. riscv64)
|
||||
can't natively run programs targeting a CPU that is
|
||||
part of the same family (e.g. riscv32).
|
||||
--preserve-argv0 preserve argv[0]
|
||||
|
||||
To import templates with update-binfmts, use :
|
||||
@@ -309,7 +312,13 @@ EOF
|
||||
|
||||
qemu_set_binfmts() {
|
||||
# probe cpu type
|
||||
host_family=$(qemu_get_family)
|
||||
host_cpu=$(qemu_normalize ${HOST_ARCH:-$(uname -m)})
|
||||
host_family=$(eval echo \$${host_cpu}_family)
|
||||
|
||||
if [ "$host_family" = "" ] ; then
|
||||
echo "INTERNAL ERROR: unknown host cpu $host_cpu" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# register the interpreter for each cpu except for the native one
|
||||
|
||||
@@ -318,20 +327,28 @@ qemu_set_binfmts() {
|
||||
mask=$(eval echo \$${cpu}_mask)
|
||||
family=$(eval echo \$${cpu}_family)
|
||||
|
||||
target="$cpu"
|
||||
if [ "$cpu" = "i486" ] ; then
|
||||
target="i386"
|
||||
fi
|
||||
|
||||
qemu="$QEMU_PATH/qemu-$target$QEMU_SUFFIX"
|
||||
|
||||
if [ "$magic" = "" ] || [ "$mask" = "" ] || [ "$family" = "" ] ; then
|
||||
echo "INTERNAL ERROR: unknown cpu $cpu" 1>&2
|
||||
continue
|
||||
fi
|
||||
|
||||
qemu="$QEMU_PATH/qemu-$cpu"
|
||||
if [ "$cpu" = "i486" ] ; then
|
||||
qemu="$QEMU_PATH/qemu-i386"
|
||||
if [ "$host_family" = "$family" ] ; then
|
||||
# When --ignore-family is used, we have to generate rules even
|
||||
# for targets that are in the same family as the host CPU. The
|
||||
# only exception is of course when the CPU types exactly match
|
||||
if [ "$target" = "$host_cpu" ] || [ "$IGNORE_FAMILY" = "no" ] ; then
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
qemu="$qemu$QEMU_SUFFIX"
|
||||
if [ "$host_family" != "$family" ] ; then
|
||||
$BINFMT_SET
|
||||
fi
|
||||
$BINFMT_SET
|
||||
done
|
||||
}
|
||||
|
||||
@@ -346,10 +363,11 @@ CREDENTIAL=no
|
||||
PERSISTENT=no
|
||||
PRESERVE_ARG0=no
|
||||
QEMU_SUFFIX=""
|
||||
IGNORE_FAMILY=no
|
||||
|
||||
_longopts="debian,systemd:,qemu-path:,qemu-suffix:,exportdir:,help,credential:,\
|
||||
persistent:,preserve-argv0:"
|
||||
options=$(getopt -o ds:Q:S:e:hc:p:g:F: -l ${_longopts} -- "$@")
|
||||
persistent:,preserve-argv0:,ignore-family:"
|
||||
options=$(getopt -o ds:Q:S:e:hc:p:g:F:i: -l ${_longopts} -- "$@")
|
||||
eval set -- "$options"
|
||||
|
||||
while true ; do
|
||||
@@ -409,6 +427,10 @@ while true ; do
|
||||
shift
|
||||
PRESERVE_ARG0="$1"
|
||||
;;
|
||||
-i|--ignore-family)
|
||||
shift
|
||||
IGNORE_FAMILY="$1"
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
|
||||
@@ -45,3 +45,5 @@ coroutine.CoroutineBt()
|
||||
# Default to silently passing through SIGUSR1, because QEMU sends it
|
||||
# to itself a lot.
|
||||
gdb.execute('handle SIGUSR1 pass noprint nostop')
|
||||
# Always print full stack for python errors, easier to debug and report issues
|
||||
gdb.execute('set python print-stack full')
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This script is executed when a guest agent receives fsfreeze-freeze and
|
||||
# fsfreeze-thaw command, if it is specified in --fsfreeze-hook (-F)
|
||||
# option of qemu-ga or placed in default path (/etc/qemu/fsfreeze-hook).
|
||||
# When the agent receives fsfreeze-freeze request, this script is issued with
|
||||
# "freeze" argument before the filesystem is frozen. And for fsfreeze-thaw
|
||||
# request, it is issued with "thaw" argument after filesystem is thawed.
|
||||
# This script is executed when the guest agent receives fsfreeze-freeze and
|
||||
# fsfreeze-thaw commands, provided that the --fsfreeze-hook (-F) option of
|
||||
# qemu-ga is specified and the script is placed in /etc/qemu/fsfreeze-hook or in
|
||||
# the path specified together with -F. When the agent receives fsfreeze-freeze
|
||||
# requests, this script is called with "freeze" as its argument before the
|
||||
# filesystem is frozen. And for fsfreeze-thaw requests, it is called with "thaw"
|
||||
# as its argument after the filesystem is thawed.
|
||||
|
||||
LOGFILE=/var/log/qga-fsfreeze-hook.log
|
||||
FSFREEZE_D=$(dirname -- "$0")/fsfreeze-hook.d
|
||||
@@ -19,15 +20,43 @@ is_ignored_file() {
|
||||
return 1
|
||||
}
|
||||
|
||||
USE_SYSLOG=0
|
||||
# if log file is not writable, fallback to syslog
|
||||
[ ! -w "$LOGFILE" ] && USE_SYSLOG=1
|
||||
# try to update log file and fallback to syslog if it fails
|
||||
touch "$LOGFILE" &>/dev/null || USE_SYSLOG=1
|
||||
|
||||
# Ensure the log file is writable, fallback to syslog if not
|
||||
log_message() {
|
||||
local message="$1"
|
||||
if [ "$USE_SYSLOG" -eq 0 ]; then
|
||||
printf "%s: %s\n" "$(date)" "$message" >>"$LOGFILE"
|
||||
else
|
||||
logger -t qemu-ga-freeze-hook "$message"
|
||||
fi
|
||||
}
|
||||
|
||||
# Iterate executables in directory "fsfreeze-hook.d" with the specified args
|
||||
[ ! -d "$FSFREEZE_D" ] && exit 0
|
||||
|
||||
for file in "$FSFREEZE_D"/* ; do
|
||||
is_ignored_file "$file" && continue
|
||||
[ -x "$file" ] || continue
|
||||
printf "$(date): execute $file $@\n" >>$LOGFILE
|
||||
"$file" "$@" >>$LOGFILE 2>&1
|
||||
STATUS=$?
|
||||
printf "$(date): $file finished with status=$STATUS\n" >>$LOGFILE
|
||||
|
||||
log_message "Executing $file $@"
|
||||
if [ "$USE_SYSLOG" -eq 0 ]; then
|
||||
"$file" "$@" >>"$LOGFILE" 2>&1
|
||||
STATUS=$?
|
||||
else
|
||||
"$file" "$@" 2>&1 | logger -t qemu-ga-freeze-hook
|
||||
STATUS=${PIPESTATUS[0]}
|
||||
fi
|
||||
|
||||
if [ $STATUS -ne 0 ]; then
|
||||
log_message "Error: $file finished with status=$STATUS"
|
||||
else
|
||||
log_message "$file finished successfully"
|
||||
fi
|
||||
done
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Extract QEMU Plugin API symbols from a header file
|
||||
#
|
||||
|
||||
@@ -56,6 +56,7 @@ def tapset_dir(binary):
|
||||
|
||||
|
||||
def cmd_run(args):
|
||||
stap = which("stap")
|
||||
prefix = probe_prefix(args.binary)
|
||||
tapsets = tapset_dir(args.binary)
|
||||
|
||||
@@ -76,7 +77,7 @@ def cmd_run(args):
|
||||
|
||||
# We request an 8MB buffer, since the stap default 1MB buffer
|
||||
# can be easily overflowed by frequently firing QEMU traces
|
||||
stapargs = ["stap", "-s", "8", "-I", tapsets ]
|
||||
stapargs = [stap, "-s", "8", "-I", tapsets ]
|
||||
if args.pid is not None:
|
||||
stapargs.extend(["-x", args.pid])
|
||||
stapargs.extend(["-e", script])
|
||||
@@ -84,6 +85,7 @@ def cmd_run(args):
|
||||
|
||||
|
||||
def cmd_list(args):
|
||||
stap = which("stap")
|
||||
tapsets = tapset_dir(args.binary)
|
||||
|
||||
if args.verbose:
|
||||
@@ -96,7 +98,7 @@ def cmd_list(args):
|
||||
|
||||
if verbose:
|
||||
print("Listing probes with name '%s'" % script)
|
||||
proc = subprocess.Popen(["stap", "-I", tapsets, "-l", script],
|
||||
proc = subprocess.Popen([stap, "-I", tapsets, "-l", script],
|
||||
stdout=subprocess.PIPE,
|
||||
universal_newlines=True)
|
||||
out, err = proc.communicate()
|
||||
|
||||
@@ -13,28 +13,9 @@ import gdb
|
||||
|
||||
VOID_PTR = gdb.lookup_type('void').pointer()
|
||||
|
||||
def get_fs_base():
|
||||
'''Fetch %fs base value using arch_prctl(ARCH_GET_FS). This is
|
||||
pthread_self().'''
|
||||
# %rsp - 120 is scratch space according to the SystemV ABI
|
||||
old = gdb.parse_and_eval('*(uint64_t*)($rsp - 120)')
|
||||
gdb.execute('call (int)arch_prctl(0x1003, $rsp - 120)', False, True)
|
||||
fs_base = gdb.parse_and_eval('*(uint64_t*)($rsp - 120)')
|
||||
gdb.execute('set *(uint64_t*)($rsp - 120) = %s' % old, False, True)
|
||||
return fs_base
|
||||
|
||||
def pthread_self():
|
||||
'''Fetch pthread_self() from the glibc start_thread function.'''
|
||||
f = gdb.newest_frame()
|
||||
while f.name() != 'start_thread':
|
||||
f = f.older()
|
||||
if f is None:
|
||||
return get_fs_base()
|
||||
|
||||
try:
|
||||
return f.read_var("arg")
|
||||
except ValueError:
|
||||
return get_fs_base()
|
||||
'''Fetch the base address of TLS.'''
|
||||
return gdb.parse_and_eval("$fs_base")
|
||||
|
||||
def get_glibc_pointer_guard():
|
||||
'''Fetch glibc pointer guard value'''
|
||||
@@ -65,9 +46,60 @@ def get_jmpbuf_regs(jmpbuf):
|
||||
'r15': jmpbuf[JB_R15],
|
||||
'rip': glibc_ptr_demangle(jmpbuf[JB_PC], pointer_guard) }
|
||||
|
||||
def bt_jmpbuf(jmpbuf):
|
||||
'''Backtrace a jmpbuf'''
|
||||
regs = get_jmpbuf_regs(jmpbuf)
|
||||
def symbol_lookup(addr):
|
||||
# Example: "__clone3 + 44 in section .text of /lib64/libc.so.6"
|
||||
result = gdb.execute(f"info symbol {hex(addr)}", to_string=True).strip()
|
||||
try:
|
||||
if "+" in result:
|
||||
(func, result) = result.split(" + ")
|
||||
(offset, result) = result.split(" in ")
|
||||
else:
|
||||
offset = "0"
|
||||
(func, result) = result.split(" in ")
|
||||
func_str = f"{func}<+{offset}> ()"
|
||||
except:
|
||||
return f"??? ({result})"
|
||||
|
||||
# Example: Line 321 of "../util/coroutine-ucontext.c" starts at address
|
||||
# 0x55cf3894d993 <qemu_coroutine_switch+99> and ends at 0x55cf3894d9ab
|
||||
# <qemu_coroutine_switch+123>.
|
||||
result = gdb.execute(f"info line *{hex(addr)}", to_string=True).strip()
|
||||
if not result.startswith("Line "):
|
||||
return func_str
|
||||
result = result[5:]
|
||||
|
||||
try:
|
||||
result = result.split(" starts ")[0]
|
||||
(line, path) = result.split(" of ")
|
||||
path = path.replace("\"", "")
|
||||
except:
|
||||
return func_str
|
||||
|
||||
return f"{func_str} at {path}:{line}"
|
||||
|
||||
def dump_backtrace(regs):
|
||||
'''
|
||||
Backtrace dump with raw registers, mimic GDB command 'bt'.
|
||||
'''
|
||||
# Here only rbp and rip that matter..
|
||||
rbp = regs['rbp']
|
||||
rip = regs['rip']
|
||||
i = 0
|
||||
|
||||
while rbp:
|
||||
# For all return addresses on stack, we want to look up symbol/line
|
||||
# on the CALL command, because the return address is the next
|
||||
# instruction instead of the CALL. Here -1 would work for any
|
||||
# sized CALL instruction.
|
||||
print(f"#{i} {hex(rip)} in {symbol_lookup(rip if i == 0 else rip-1)}")
|
||||
rip = gdb.parse_and_eval(f"*(uint64_t *)(uint64_t)({hex(rbp)} + 8)")
|
||||
rbp = gdb.parse_and_eval(f"*(uint64_t *)(uint64_t)({hex(rbp)})")
|
||||
i += 1
|
||||
|
||||
def dump_backtrace_live(regs):
|
||||
'''
|
||||
Backtrace dump with gdb's 'bt' command, only usable in a live session.
|
||||
'''
|
||||
old = dict()
|
||||
|
||||
# remember current stack frame and select the topmost
|
||||
@@ -88,6 +120,17 @@ def bt_jmpbuf(jmpbuf):
|
||||
|
||||
selected_frame.select()
|
||||
|
||||
def bt_jmpbuf(jmpbuf):
|
||||
'''Backtrace a jmpbuf'''
|
||||
regs = get_jmpbuf_regs(jmpbuf)
|
||||
try:
|
||||
# This reuses gdb's "bt" command, which can be slightly prettier
|
||||
# but only works with live sessions.
|
||||
dump_backtrace_live(regs)
|
||||
except:
|
||||
# If above doesn't work, fallback to poor man's unwind
|
||||
dump_backtrace(regs)
|
||||
|
||||
def co_cast(co):
|
||||
return co.cast(gdb.lookup_type('CoroutineUContext').pointer())
|
||||
|
||||
@@ -120,10 +163,15 @@ class CoroutineBt(gdb.Command):
|
||||
|
||||
gdb.execute("bt")
|
||||
|
||||
if gdb.parse_and_eval("qemu_in_coroutine()") == False:
|
||||
return
|
||||
try:
|
||||
# This only works with a live session
|
||||
co_ptr = gdb.parse_and_eval("qemu_coroutine_self()")
|
||||
except:
|
||||
# Fallback to use hard-coded ucontext vars if it's coredump
|
||||
co_ptr = gdb.parse_and_eval("co_tls_current")
|
||||
|
||||
co_ptr = gdb.parse_and_eval("qemu_coroutine_self()")
|
||||
if co_ptr == False:
|
||||
return
|
||||
|
||||
while True:
|
||||
co = co_cast(co_ptr)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# GDB debugging support, TCG status
|
||||
#
|
||||
# Copyright 2016 Linaro Ltd
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# GDB debugging support
|
||||
#
|
||||
# Copyright 2017 Linaro Ltd
|
||||
|
||||
Executable
+703
@@ -0,0 +1,703 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# pylint: disable=C0103,E0213,E1135,E1136,E1137,R0902,R0903,R0912,R0913,R0917
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#
|
||||
# Copyright (C) 2024-2025 Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
|
||||
|
||||
"""
|
||||
Helper classes to be used by ghes_inject command classes.
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
|
||||
from datetime import datetime
|
||||
from os import path as os_path
|
||||
|
||||
try:
|
||||
qemu_dir = os_path.abspath(os_path.dirname(os_path.dirname(__file__)))
|
||||
sys.path.append(os_path.join(qemu_dir, 'python'))
|
||||
|
||||
from qemu.qmp.legacy import QEMUMonitorProtocol
|
||||
|
||||
except ModuleNotFoundError as exc:
|
||||
print(f"Module '{exc.name}' not found.")
|
||||
print("Try export PYTHONPATH=top-qemu-dir/python or run from top-qemu-dir")
|
||||
sys.exit(1)
|
||||
|
||||
from base64 import b64encode
|
||||
|
||||
class util:
|
||||
"""
|
||||
Ancillary functions to deal with bitmaps, parse arguments,
|
||||
generate GUID and encode data on a bytearray buffer.
|
||||
"""
|
||||
|
||||
#
|
||||
# Helper routines to handle multiple choice arguments
|
||||
#
|
||||
def get_choice(name, value, choices, suffixes=None, bitmask=True):
|
||||
"""Produce a list from multiple choice argument"""
|
||||
|
||||
new_values = 0
|
||||
|
||||
if not value:
|
||||
return new_values
|
||||
|
||||
for val in value.split(","):
|
||||
val = val.lower()
|
||||
|
||||
if suffixes:
|
||||
for suffix in suffixes:
|
||||
val = val.removesuffix(suffix)
|
||||
|
||||
if val not in choices.keys():
|
||||
if suffixes:
|
||||
for suffix in suffixes:
|
||||
if val + suffix in choices.keys():
|
||||
val += suffix
|
||||
break
|
||||
|
||||
if val not in choices.keys():
|
||||
sys.exit(f"Error on '{name}': choice '{val}' is invalid.")
|
||||
|
||||
val = choices[val]
|
||||
|
||||
if bitmask:
|
||||
new_values |= val
|
||||
else:
|
||||
if new_values:
|
||||
sys.exit(f"Error on '{name}': only one value is accepted.")
|
||||
|
||||
new_values = val
|
||||
|
||||
return new_values
|
||||
|
||||
def get_array(name, values, max_val=None):
|
||||
"""Add numbered hashes from integer lists into an array"""
|
||||
|
||||
array = []
|
||||
|
||||
for value in values:
|
||||
for val in value.split(","):
|
||||
try:
|
||||
val = int(val, 0)
|
||||
except ValueError:
|
||||
sys.exit(f"Error on '{name}': {val} is not an integer")
|
||||
|
||||
if val < 0:
|
||||
sys.exit(f"Error on '{name}': {val} is not unsigned")
|
||||
|
||||
if max_val and val > max_val:
|
||||
sys.exit(f"Error on '{name}': {val} is too little")
|
||||
|
||||
array.append(val)
|
||||
|
||||
return array
|
||||
|
||||
def get_mult_array(mult, name, values, allow_zero=False, max_val=None):
|
||||
"""Add numbered hashes from integer lists"""
|
||||
|
||||
if not allow_zero:
|
||||
if not values:
|
||||
return
|
||||
else:
|
||||
if values is None:
|
||||
return
|
||||
|
||||
if not values:
|
||||
i = 0
|
||||
if i not in mult:
|
||||
mult[i] = {}
|
||||
|
||||
mult[i][name] = []
|
||||
return
|
||||
|
||||
i = 0
|
||||
for value in values:
|
||||
for val in value.split(","):
|
||||
try:
|
||||
val = int(val, 0)
|
||||
except ValueError:
|
||||
sys.exit(f"Error on '{name}': {val} is not an integer")
|
||||
|
||||
if val < 0:
|
||||
sys.exit(f"Error on '{name}': {val} is not unsigned")
|
||||
|
||||
if max_val and val > max_val:
|
||||
sys.exit(f"Error on '{name}': {val} is too little")
|
||||
|
||||
if i not in mult:
|
||||
mult[i] = {}
|
||||
|
||||
if name not in mult[i]:
|
||||
mult[i][name] = []
|
||||
|
||||
mult[i][name].append(val)
|
||||
|
||||
i += 1
|
||||
|
||||
|
||||
def get_mult_choices(mult, name, values, choices,
|
||||
suffixes=None, allow_zero=False):
|
||||
"""Add numbered hashes from multiple choice arguments"""
|
||||
|
||||
if not allow_zero:
|
||||
if not values:
|
||||
return
|
||||
else:
|
||||
if values is None:
|
||||
return
|
||||
|
||||
i = 0
|
||||
for val in values:
|
||||
new_values = util.get_choice(name, val, choices, suffixes)
|
||||
|
||||
if i not in mult:
|
||||
mult[i] = {}
|
||||
|
||||
mult[i][name] = new_values
|
||||
i += 1
|
||||
|
||||
|
||||
def get_mult_int(mult, name, values, allow_zero=False):
|
||||
"""Add numbered hashes from integer arguments"""
|
||||
if not allow_zero:
|
||||
if not values:
|
||||
return
|
||||
else:
|
||||
if values is None:
|
||||
return
|
||||
|
||||
i = 0
|
||||
for val in values:
|
||||
try:
|
||||
val = int(val, 0)
|
||||
except ValueError:
|
||||
sys.exit(f"Error on '{name}': {val} is not an integer")
|
||||
|
||||
if val < 0:
|
||||
sys.exit(f"Error on '{name}': {val} is not unsigned")
|
||||
|
||||
if i not in mult:
|
||||
mult[i] = {}
|
||||
|
||||
mult[i][name] = val
|
||||
i += 1
|
||||
|
||||
|
||||
#
|
||||
# Data encode helper functions
|
||||
#
|
||||
def bit(b):
|
||||
"""Simple macro to define a bit on a bitmask"""
|
||||
return 1 << b
|
||||
|
||||
|
||||
def data_add(data, value, num_bytes):
|
||||
"""Adds bytes from value inside a bitarray"""
|
||||
|
||||
data.extend(value.to_bytes(num_bytes, byteorder="little")) # pylint: disable=E1101
|
||||
|
||||
def dump_bytearray(name, data):
|
||||
"""Does an hexdump of a byte array, grouping in bytes"""
|
||||
|
||||
print(f"{name} ({len(data)} bytes):")
|
||||
|
||||
for ln_start in range(0, len(data), 16):
|
||||
ln_end = min(ln_start + 16, len(data))
|
||||
print(f" {ln_start:08x} ", end="")
|
||||
for i in range(ln_start, ln_end):
|
||||
print(f"{data[i]:02x} ", end="")
|
||||
for i in range(ln_end, ln_start + 16):
|
||||
print(" ", end="")
|
||||
print(" ", end="")
|
||||
for i in range(ln_start, ln_end):
|
||||
if data[i] >= 32 and data[i] < 127:
|
||||
print(chr(data[i]), end="")
|
||||
else:
|
||||
print(".", end="")
|
||||
|
||||
print()
|
||||
print()
|
||||
|
||||
def time(string):
|
||||
"""Handle BCD timestamps used on Generic Error Data Block"""
|
||||
|
||||
time = None
|
||||
|
||||
# Formats to be used when parsing time stamps
|
||||
formats = [
|
||||
"%Y-%m-%d %H:%M:%S",
|
||||
]
|
||||
|
||||
if string == "now":
|
||||
time = datetime.now()
|
||||
|
||||
if time is None:
|
||||
for fmt in formats:
|
||||
try:
|
||||
time = datetime.strptime(string, fmt)
|
||||
break
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if time is None:
|
||||
raise ValueError("Invalid time format")
|
||||
|
||||
return time
|
||||
|
||||
class guid:
|
||||
"""
|
||||
Simple class to handle GUID fields.
|
||||
"""
|
||||
|
||||
def __init__(self, time_low, time_mid, time_high, nodes):
|
||||
"""Initialize a GUID value"""
|
||||
|
||||
assert len(nodes) == 8
|
||||
|
||||
self.time_low = time_low
|
||||
self.time_mid = time_mid
|
||||
self.time_high = time_high
|
||||
self.nodes = nodes
|
||||
|
||||
@classmethod
|
||||
def UUID(cls, guid_str):
|
||||
"""Initialize a GUID using a string on its standard format"""
|
||||
|
||||
if len(guid_str) != 36:
|
||||
print("Size not 36")
|
||||
raise ValueError('Invalid GUID size')
|
||||
|
||||
# It is easier to parse without separators. So, drop them
|
||||
guid_str = guid_str.replace('-', '')
|
||||
|
||||
if len(guid_str) != 32:
|
||||
print("Size not 32", guid_str, len(guid_str))
|
||||
raise ValueError('Invalid GUID hex size')
|
||||
|
||||
time_low = 0
|
||||
time_mid = 0
|
||||
time_high = 0
|
||||
nodes = []
|
||||
|
||||
for i in reversed(range(16, 32, 2)):
|
||||
h = guid_str[i:i + 2]
|
||||
value = int(h, 16)
|
||||
nodes.insert(0, value)
|
||||
|
||||
time_high = int(guid_str[12:16], 16)
|
||||
time_mid = int(guid_str[8:12], 16)
|
||||
time_low = int(guid_str[0:8], 16)
|
||||
|
||||
return cls(time_low, time_mid, time_high, nodes)
|
||||
|
||||
def __str__(self):
|
||||
"""Output a GUID value on its default string representation"""
|
||||
|
||||
clock = self.nodes[0] << 8 | self.nodes[1]
|
||||
|
||||
node = 0
|
||||
for i in range(2, len(self.nodes)):
|
||||
node = node << 8 | self.nodes[i]
|
||||
|
||||
s = f"{self.time_low:08x}-{self.time_mid:04x}-"
|
||||
s += f"{self.time_high:04x}-{clock:04x}-{node:012x}"
|
||||
return s
|
||||
|
||||
def to_bytes(self):
|
||||
"""Output a GUID value in bytes"""
|
||||
|
||||
data = bytearray()
|
||||
|
||||
util.data_add(data, self.time_low, 4)
|
||||
util.data_add(data, self.time_mid, 2)
|
||||
util.data_add(data, self.time_high, 2)
|
||||
data.extend(bytearray(self.nodes))
|
||||
|
||||
return data
|
||||
|
||||
class qmp:
|
||||
"""
|
||||
Opens a connection and send/receive QMP commands.
|
||||
"""
|
||||
|
||||
def send_cmd(self, command, args=None, may_open=False, return_error=True):
|
||||
"""Send a command to QMP, optinally opening a connection"""
|
||||
|
||||
if may_open:
|
||||
self._connect()
|
||||
elif not self.connected:
|
||||
return False
|
||||
|
||||
msg = { 'execute': command }
|
||||
if args:
|
||||
msg['arguments'] = args
|
||||
|
||||
try:
|
||||
obj = self.qmp_monitor.cmd_obj(msg)
|
||||
# Can we use some other exception class here?
|
||||
except Exception as e: # pylint: disable=W0718
|
||||
print(f"Command: {command}")
|
||||
print(f"Failed to inject error: {e}.")
|
||||
return None
|
||||
|
||||
if "return" in obj:
|
||||
if isinstance(obj.get("return"), dict):
|
||||
if obj["return"]:
|
||||
return obj["return"]
|
||||
return "OK"
|
||||
|
||||
return obj["return"]
|
||||
|
||||
if isinstance(obj.get("error"), dict):
|
||||
error = obj["error"]
|
||||
if return_error:
|
||||
print(f"Command: {msg}")
|
||||
print(f'{error["class"]}: {error["desc"]}')
|
||||
else:
|
||||
print(json.dumps(obj))
|
||||
|
||||
return None
|
||||
|
||||
def _close(self):
|
||||
"""Shutdown and close the socket, if opened"""
|
||||
if not self.connected:
|
||||
return
|
||||
|
||||
self.qmp_monitor.close()
|
||||
self.connected = False
|
||||
|
||||
def _connect(self):
|
||||
"""Connect to a QMP TCP/IP port, if not connected yet"""
|
||||
|
||||
if self.connected:
|
||||
return True
|
||||
|
||||
try:
|
||||
self.qmp_monitor.connect(negotiate=True)
|
||||
except ConnectionError:
|
||||
sys.exit(f"Can't connect to QMP host {self.host}:{self.port}")
|
||||
|
||||
self.connected = True
|
||||
|
||||
return True
|
||||
|
||||
BLOCK_STATUS_BITS = {
|
||||
"uncorrectable": util.bit(0),
|
||||
"correctable": util.bit(1),
|
||||
"multi-uncorrectable": util.bit(2),
|
||||
"multi-correctable": util.bit(3),
|
||||
}
|
||||
|
||||
ERROR_SEVERITY = {
|
||||
"recoverable": 0,
|
||||
"fatal": 1,
|
||||
"corrected": 2,
|
||||
"none": 3,
|
||||
}
|
||||
|
||||
VALIDATION_BITS = {
|
||||
"fru-id": util.bit(0),
|
||||
"fru-text": util.bit(1),
|
||||
"timestamp": util.bit(2),
|
||||
}
|
||||
|
||||
GEDB_FLAGS_BITS = {
|
||||
"recovered": util.bit(0),
|
||||
"prev-error": util.bit(1),
|
||||
"simulated": util.bit(2),
|
||||
}
|
||||
|
||||
GENERIC_DATA_SIZE = 72
|
||||
|
||||
def argparse(parser):
|
||||
"""Prepare a parser group to query generic error data"""
|
||||
|
||||
block_status_bits = ",".join(qmp.BLOCK_STATUS_BITS.keys())
|
||||
error_severity_enum = ",".join(qmp.ERROR_SEVERITY.keys())
|
||||
validation_bits = ",".join(qmp.VALIDATION_BITS.keys())
|
||||
gedb_flags_bits = ",".join(qmp.GEDB_FLAGS_BITS.keys())
|
||||
|
||||
g_gen = parser.add_argument_group("Generic Error Data") # pylint: disable=E1101
|
||||
g_gen.add_argument("--block-status",
|
||||
help=f"block status bits: {block_status_bits}")
|
||||
g_gen.add_argument("--raw-data", nargs="+",
|
||||
help="Raw data inside the Error Status Block")
|
||||
g_gen.add_argument("--error-severity", "--severity",
|
||||
help=f"error severity: {error_severity_enum}")
|
||||
g_gen.add_argument("--gen-err-valid-bits",
|
||||
"--generic-error-validation-bits",
|
||||
help=f"validation bits: {validation_bits}")
|
||||
g_gen.add_argument("--fru-id", type=guid.UUID,
|
||||
help="GUID representing a physical device")
|
||||
g_gen.add_argument("--fru-text",
|
||||
help="ASCII string identifying the FRU hardware")
|
||||
g_gen.add_argument("--timestamp", type=util.time,
|
||||
help="Time when the error info was collected")
|
||||
g_gen.add_argument("--precise", "--precise-timestamp",
|
||||
action='store_true',
|
||||
help="Marks the timestamp as precise if --timestamp is used")
|
||||
g_gen.add_argument("--gedb-flags",
|
||||
help=f"General Error Data Block flags: {gedb_flags_bits}")
|
||||
|
||||
def set_args(self, args):
|
||||
"""Set the arguments optionally defined via self.argparse()"""
|
||||
|
||||
if args.block_status:
|
||||
self.block_status = util.get_choice(name="block-status",
|
||||
value=args.block_status,
|
||||
choices=self.BLOCK_STATUS_BITS,
|
||||
bitmask=False)
|
||||
if args.raw_data:
|
||||
self.raw_data = util.get_array("raw-data", args.raw_data,
|
||||
max_val=255)
|
||||
print(self.raw_data)
|
||||
|
||||
if args.error_severity:
|
||||
self.error_severity = util.get_choice(name="error-severity",
|
||||
value=args.error_severity,
|
||||
choices=self.ERROR_SEVERITY,
|
||||
bitmask=False)
|
||||
|
||||
if args.fru_id:
|
||||
self.fru_id = args.fru_id.to_bytes()
|
||||
if not args.gen_err_valid_bits:
|
||||
self.validation_bits |= self.VALIDATION_BITS["fru-id"]
|
||||
|
||||
if args.fru_text:
|
||||
text = bytearray(args.fru_text.encode('ascii'))
|
||||
if len(text) > 20:
|
||||
sys.exit("FRU text is too big to fit")
|
||||
|
||||
self.fru_text = text
|
||||
if not args.gen_err_valid_bits:
|
||||
self.validation_bits |= self.VALIDATION_BITS["fru-text"]
|
||||
|
||||
if args.timestamp:
|
||||
time = args.timestamp
|
||||
century = int(time.year / 100)
|
||||
|
||||
bcd = bytearray()
|
||||
util.data_add(bcd, (time.second // 10) << 4 | (time.second % 10), 1)
|
||||
util.data_add(bcd, (time.minute // 10) << 4 | (time.minute % 10), 1)
|
||||
util.data_add(bcd, (time.hour // 10) << 4 | (time.hour % 10), 1)
|
||||
|
||||
if args.precise:
|
||||
util.data_add(bcd, 1, 1)
|
||||
else:
|
||||
util.data_add(bcd, 0, 1)
|
||||
|
||||
util.data_add(bcd, (time.day // 10) << 4 | (time.day % 10), 1)
|
||||
util.data_add(bcd, (time.month // 10) << 4 | (time.month % 10), 1)
|
||||
util.data_add(bcd,
|
||||
((time.year % 100) // 10) << 4 | (time.year % 10), 1)
|
||||
util.data_add(bcd, ((century % 100) // 10) << 4 | (century % 10), 1)
|
||||
|
||||
self.timestamp = bcd
|
||||
if not args.gen_err_valid_bits:
|
||||
self.validation_bits |= self.VALIDATION_BITS["timestamp"]
|
||||
|
||||
if args.gen_err_valid_bits:
|
||||
self.validation_bits = util.get_choice(name="validation",
|
||||
value=args.gen_err_valid_bits,
|
||||
choices=self.VALIDATION_BITS)
|
||||
|
||||
def __init__(self, host, port, debug=False):
|
||||
"""Initialize variables used by the QMP send logic"""
|
||||
|
||||
self.connected = False
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.debug = debug
|
||||
|
||||
# ACPI 6.1: 18.3.2.7.1 Generic Error Data: Generic Error Status Block
|
||||
self.block_status = self.BLOCK_STATUS_BITS["uncorrectable"]
|
||||
self.raw_data = []
|
||||
self.error_severity = self.ERROR_SEVERITY["recoverable"]
|
||||
|
||||
# ACPI 6.1: 18.3.2.7.1 Generic Error Data: Generic Error Data Entry
|
||||
self.validation_bits = 0
|
||||
self.flags = 0
|
||||
self.fru_id = bytearray(16)
|
||||
self.fru_text = bytearray(20)
|
||||
self.timestamp = bytearray(8)
|
||||
|
||||
self.qmp_monitor = QEMUMonitorProtocol(address=(self.host, self.port))
|
||||
|
||||
#
|
||||
# Socket QMP send command
|
||||
#
|
||||
def send_cper_raw(self, cper_data):
|
||||
"""Send a raw CPER data to QEMU though QMP TCP socket"""
|
||||
|
||||
data = b64encode(bytes(cper_data)).decode('ascii')
|
||||
|
||||
cmd_arg = {
|
||||
'cper': data
|
||||
}
|
||||
|
||||
self._connect()
|
||||
|
||||
if self.send_cmd("inject-ghes-v2-error", cmd_arg):
|
||||
print("Error injected.")
|
||||
|
||||
def send_cper(self, notif_type, payload):
|
||||
"""Send commands to QEMU though QMP TCP socket"""
|
||||
|
||||
# Fill CPER record header
|
||||
|
||||
# NOTE: bits 4 to 13 of block status contain the number of
|
||||
# data entries in the data section. This is currently unsupported.
|
||||
|
||||
cper_length = len(payload)
|
||||
data_length = cper_length + len(self.raw_data) + self.GENERIC_DATA_SIZE
|
||||
|
||||
# Generic Error Data Entry
|
||||
gede = bytearray()
|
||||
|
||||
gede.extend(notif_type.to_bytes())
|
||||
util.data_add(gede, self.error_severity, 4)
|
||||
util.data_add(gede, 0x300, 2)
|
||||
util.data_add(gede, self.validation_bits, 1)
|
||||
util.data_add(gede, self.flags, 1)
|
||||
util.data_add(gede, cper_length, 4)
|
||||
gede.extend(self.fru_id)
|
||||
gede.extend(self.fru_text)
|
||||
gede.extend(self.timestamp)
|
||||
|
||||
# Generic Error Status Block
|
||||
gebs = bytearray()
|
||||
|
||||
if self.raw_data:
|
||||
raw_data_offset = len(gebs)
|
||||
else:
|
||||
raw_data_offset = 0
|
||||
|
||||
util.data_add(gebs, self.block_status, 4)
|
||||
util.data_add(gebs, raw_data_offset, 4)
|
||||
util.data_add(gebs, len(self.raw_data), 4)
|
||||
util.data_add(gebs, data_length, 4)
|
||||
util.data_add(gebs, self.error_severity, 4)
|
||||
|
||||
cper_data = bytearray()
|
||||
cper_data.extend(gebs)
|
||||
cper_data.extend(gede)
|
||||
cper_data.extend(bytearray(self.raw_data))
|
||||
cper_data.extend(bytearray(payload))
|
||||
|
||||
if self.debug:
|
||||
print(f"GUID: {notif_type}")
|
||||
|
||||
util.dump_bytearray("Generic Error Status Block", gebs)
|
||||
util.dump_bytearray("Generic Error Data Entry", gede)
|
||||
|
||||
if self.raw_data:
|
||||
util.dump_bytearray("Raw data", bytearray(self.raw_data))
|
||||
|
||||
util.dump_bytearray("Payload", payload)
|
||||
|
||||
self.send_cper_raw(cper_data)
|
||||
|
||||
|
||||
def search_qom(self, path, prop, regex):
|
||||
"""
|
||||
Return a list of devices that match path array like:
|
||||
|
||||
/machine/unattached/device
|
||||
/machine/peripheral-anon/device
|
||||
...
|
||||
"""
|
||||
|
||||
found = []
|
||||
|
||||
i = 0
|
||||
while 1:
|
||||
dev = f"{path}[{i}]"
|
||||
args = {
|
||||
'path': dev,
|
||||
'property': prop
|
||||
}
|
||||
ret = self.send_cmd("qom-get", args, may_open=True,
|
||||
return_error=False)
|
||||
if not ret:
|
||||
break
|
||||
|
||||
if isinstance(ret, str):
|
||||
if regex.search(ret):
|
||||
found.append(dev)
|
||||
|
||||
i += 1
|
||||
if i > 10000:
|
||||
print("Too many objects returned by qom-get!")
|
||||
break
|
||||
|
||||
return found
|
||||
|
||||
class cper_guid:
|
||||
"""
|
||||
Contains CPER GUID, as per:
|
||||
https://uefi.org/specs/UEFI/2.10/Apx_N_Common_Platform_Error_Record.html
|
||||
"""
|
||||
|
||||
CPER_PROC_GENERIC = guid(0x9876CCAD, 0x47B4, 0x4bdb,
|
||||
[0xB6, 0x5E, 0x16, 0xF1,
|
||||
0x93, 0xC4, 0xF3, 0xDB])
|
||||
|
||||
CPER_PROC_X86 = guid(0xDC3EA0B0, 0xA144, 0x4797,
|
||||
[0xB9, 0x5B, 0x53, 0xFA,
|
||||
0x24, 0x2B, 0x6E, 0x1D])
|
||||
|
||||
CPER_PROC_ITANIUM = guid(0xe429faf1, 0x3cb7, 0x11d4,
|
||||
[0xbc, 0xa7, 0x00, 0x80,
|
||||
0xc7, 0x3c, 0x88, 0x81])
|
||||
|
||||
CPER_PROC_ARM = guid(0xE19E3D16, 0xBC11, 0x11E4,
|
||||
[0x9C, 0xAA, 0xC2, 0x05,
|
||||
0x1D, 0x5D, 0x46, 0xB0])
|
||||
|
||||
CPER_PLATFORM_MEM = guid(0xA5BC1114, 0x6F64, 0x4EDE,
|
||||
[0xB8, 0x63, 0x3E, 0x83,
|
||||
0xED, 0x7C, 0x83, 0xB1])
|
||||
|
||||
CPER_PLATFORM_MEM2 = guid(0x61EC04FC, 0x48E6, 0xD813,
|
||||
[0x25, 0xC9, 0x8D, 0xAA,
|
||||
0x44, 0x75, 0x0B, 0x12])
|
||||
|
||||
CPER_PCIE = guid(0xD995E954, 0xBBC1, 0x430F,
|
||||
[0xAD, 0x91, 0xB4, 0x4D,
|
||||
0xCB, 0x3C, 0x6F, 0x35])
|
||||
|
||||
CPER_PCI_BUS = guid(0xC5753963, 0x3B84, 0x4095,
|
||||
[0xBF, 0x78, 0xED, 0xDA,
|
||||
0xD3, 0xF9, 0xC9, 0xDD])
|
||||
|
||||
CPER_PCI_DEV = guid(0xEB5E4685, 0xCA66, 0x4769,
|
||||
[0xB6, 0xA2, 0x26, 0x06,
|
||||
0x8B, 0x00, 0x13, 0x26])
|
||||
|
||||
CPER_FW_ERROR = guid(0x81212A96, 0x09ED, 0x4996,
|
||||
[0x94, 0x71, 0x8D, 0x72,
|
||||
0x9C, 0x8E, 0x69, 0xED])
|
||||
|
||||
CPER_DMA_GENERIC = guid(0x5B51FEF7, 0xC79D, 0x4434,
|
||||
[0x8F, 0x1B, 0xAA, 0x62,
|
||||
0xDE, 0x3E, 0x2C, 0x64])
|
||||
|
||||
CPER_DMA_VT = guid(0x71761D37, 0x32B2, 0x45cd,
|
||||
[0xA7, 0xD0, 0xB0, 0xFE,
|
||||
0xDD, 0x93, 0xE8, 0xCF])
|
||||
|
||||
CPER_DMA_IOMMU = guid(0x036F84E1, 0x7F37, 0x428c,
|
||||
[0xA7, 0x9E, 0x57, 0x5F,
|
||||
0xDF, 0xAA, 0x84, 0xEC])
|
||||
|
||||
CPER_CCIX_PER = guid(0x91335EF6, 0xEBFB, 0x4478,
|
||||
[0xA6, 0xA6, 0x88, 0xB7,
|
||||
0x28, 0xCF, 0x75, 0xD7])
|
||||
|
||||
CPER_CXL_PROT_ERR = guid(0x80B9EFB4, 0x52B5, 0x4DE3,
|
||||
[0xA7, 0x77, 0x68, 0x78,
|
||||
0x4B, 0x77, 0x10, 0x48])
|
||||
@@ -13,8 +13,11 @@
|
||||
# --in-place \
|
||||
# --dir .
|
||||
#
|
||||
# SPDX-FileContributor: Philippe Mathieu-Daudé <philmd@linaro.org>
|
||||
# SPDX-FileCopyrightText: 2023 Linaro Ltd.
|
||||
# Copyright (c) 2023 Linaro Ltd.
|
||||
#
|
||||
# Authors:
|
||||
# Philippe Mathieu-Daudé
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import re
|
||||
|
||||
Executable
+109
@@ -0,0 +1,109 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copied from blktests
|
||||
get_ipv4_addr()
|
||||
{
|
||||
ip -4 -o addr show dev "$1" |
|
||||
sed -n 's/.*[[:blank:]]inet[[:blank:]]*\([^[:blank:]/]*\).*/\1/p' |
|
||||
head -1 | tr -d '\n'
|
||||
}
|
||||
|
||||
get_ipv6_addr() {
|
||||
ipv6=$(ip -6 -o addr show dev "$1" |
|
||||
sed -n 's/.*[[:blank:]]inet6[[:blank:]]*\([^[:blank:]/]*\).*/\1/p' |
|
||||
head -1 | tr -d '\n')
|
||||
|
||||
[ $? -eq 0 ] || return
|
||||
|
||||
if [[ "$ipv6" =~ ^fe80: ]]; then
|
||||
echo -n "[$ipv6%$1]"
|
||||
else
|
||||
echo -n "[$ipv6]"
|
||||
fi
|
||||
}
|
||||
|
||||
# existing rdma interfaces
|
||||
rdma_interfaces()
|
||||
{
|
||||
rdma link show | sed -nE 's/^link .* netdev ([^ ]+).*$/\1 /p' |
|
||||
grep -Ev '^(lo|tun|tap)'
|
||||
}
|
||||
|
||||
# existing valid ipv4 interfaces
|
||||
ipv4_interfaces()
|
||||
{
|
||||
ip -o addr show | awk '/inet / {print $2}' | grep -Ev '^(lo|tun|tap)'
|
||||
}
|
||||
|
||||
ipv6_interfaces()
|
||||
{
|
||||
ip -o addr show | awk '/inet6 / {print $2}' | grep -Ev '^(lo|tun|tap)'
|
||||
}
|
||||
|
||||
rdma_rxe_detect()
|
||||
{
|
||||
family=$1
|
||||
for r in $(rdma_interfaces)
|
||||
do
|
||||
"$family"_interfaces | grep -qw $r && get_"$family"_addr $r && return
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
rdma_rxe_setup()
|
||||
{
|
||||
family=$1
|
||||
for i in $("$family"_interfaces)
|
||||
do
|
||||
if rdma_interfaces | grep -qw $i; then
|
||||
echo "$family: Reuse the existing rdma/rxe ${i}_rxe" \
|
||||
"for $i with $(get_"$family"_addr $i)"
|
||||
return
|
||||
fi
|
||||
|
||||
rdma link add "${i}_rxe" type rxe netdev "$i" && {
|
||||
echo "$family: Setup new rdma/rxe ${i}_rxe" \
|
||||
"for $i with $(get_"$family"_addr $i)"
|
||||
return
|
||||
}
|
||||
done
|
||||
|
||||
echo "$family: Failed to setup any new rdma/rxe link" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
rdma_rxe_clean()
|
||||
{
|
||||
modprobe -r rdma_rxe
|
||||
}
|
||||
|
||||
IP_FAMILY=${IP_FAMILY:-ipv4}
|
||||
if [ "$IP_FAMILY" != "ipv6" ] && [ "$IP_FAMILY" != "ipv4" ]; then
|
||||
echo "Unknown ip family '$IP_FAMILY', only ipv4 or ipv6 is supported." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
operation=${1:-detect}
|
||||
|
||||
command -v rdma >/dev/null || {
|
||||
echo "Command 'rdma' is not available, please install it first." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ "$operation" == "setup" ] || [ "$operation" == "clean" ]; then
|
||||
[ "$UID" == 0 ] || {
|
||||
echo "Root privilege is required to setup/clean a rdma/rxe link" >&2
|
||||
exit 1
|
||||
}
|
||||
if [ "$operation" == "setup" ]; then
|
||||
rdma_rxe_setup ipv4
|
||||
rdma_rxe_setup ipv6
|
||||
else
|
||||
rdma_rxe_clean
|
||||
fi
|
||||
elif [ "$operation" == "detect" ]; then
|
||||
rdma_rxe_detect "$IP_FAMILY"
|
||||
else
|
||||
echo "Usage: $0 [setup | detect | clean]"
|
||||
fi
|
||||
@@ -1,5 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Dump the contents of a recorded execution stream
|
||||
#
|
||||
|
||||
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env sh
|
||||
#
|
||||
# Copyright (C) 2025 Red Hat, Inc.
|
||||
#
|
||||
# Based on rust_to_clang_target() tests from rust-bindgen.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
scripts_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
. "$scripts_dir/rust-to-clang-target.sh"
|
||||
|
||||
test_case() {
|
||||
input="$1"
|
||||
expected="$2"
|
||||
result=$(rust_to_clang_target "$input")
|
||||
|
||||
if [ "$result" = "$expected" ]; then
|
||||
echo " OK: '$input' -> '$result'"
|
||||
else
|
||||
echo " FAILED: '$input'"
|
||||
echo " Expected: '$expected'"
|
||||
echo " Got: '$result'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
echo "Running tests..."
|
||||
|
||||
test_case "aarch64-apple-ios" "arm64-apple-ios"
|
||||
test_case "riscv64gc-unknown-linux-gnu" "riscv64-unknown-linux-gnu"
|
||||
test_case "riscv64imac-unknown-none-elf" "riscv64-unknown-none-elf"
|
||||
test_case "riscv32imc-unknown-none-elf" "riscv32-unknown-none-elf"
|
||||
test_case "riscv32imac-unknown-none-elf" "riscv32-unknown-none-elf"
|
||||
test_case "riscv32imafc-unknown-none-elf" "riscv32-unknown-none-elf"
|
||||
test_case "riscv32i-unknown-none-elf" "riscv32-unknown-none-elf"
|
||||
test_case "riscv32imc-esp-espidf" "riscv32-esp-elf"
|
||||
test_case "xtensa-esp32-espidf" "xtensa-esp32-elf"
|
||||
test_case "aarch64-apple-ios-sim" "arm64-apple-ios-simulator"
|
||||
test_case "aarch64-apple-tvos-sim" "arm64-apple-tvos-simulator"
|
||||
test_case "aarch64-apple-watchos-sim" "arm64-apple-watchos-simulator"
|
||||
|
||||
echo ""
|
||||
echo "All tests passed!"
|
||||
@@ -0,0 +1,60 @@
|
||||
# Copyright (C) 2025 Red Hat, Inc.
|
||||
#
|
||||
# Based on rust_to_clang_target() from rust-bindgen.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
rust_to_clang_target() {
|
||||
rust_target="$1"
|
||||
|
||||
# Split the string by hyphens
|
||||
triple_parts=""
|
||||
old_IFS="$IFS"
|
||||
IFS='-'
|
||||
for part in $rust_target; do
|
||||
triple_parts="$triple_parts $part"
|
||||
done
|
||||
IFS="$old_IFS"
|
||||
set -- $triple_parts
|
||||
|
||||
# RISC-V
|
||||
case "$1" in
|
||||
riscv32*)
|
||||
set -- "riscv32" "${2}" "${3}" "${4}"
|
||||
;;
|
||||
riscv64*)
|
||||
set -- "riscv64" "${2}" "${3}" "${4}"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Apple
|
||||
if [ "$2" = "apple" ]; then
|
||||
if [ "$1" = "aarch64" ]; then
|
||||
set -- "arm64" "${2}" "${3}" "${4}"
|
||||
fi
|
||||
if [ "$4" = "sim" ]; then
|
||||
set -- "${1}" "${2}" "${3}" "simulator"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ESP-IDF
|
||||
if [ "$3" = "espidf" ]; then
|
||||
set -- "${1}" "${2}" "elf" "${4}"
|
||||
fi
|
||||
|
||||
# Reassemble the string
|
||||
new_triple=""
|
||||
first=1
|
||||
for part in "$@"; do
|
||||
if [ -n "$part" ]; then
|
||||
if [ "$first" -eq 1 ]; then
|
||||
new_triple="$part"
|
||||
first=0
|
||||
else
|
||||
new_triple="$new_triple-$part"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo "$new_triple"
|
||||
}
|
||||
@@ -4,7 +4,7 @@ set -eu
|
||||
|
||||
cat <<EOF
|
||||
// @generated
|
||||
// This file is autogenerated by scripts/rust_root_crate.sh
|
||||
// This file is autogenerated by scripts/rust/rust_root_crate.sh
|
||||
|
||||
EOF
|
||||
|
||||
|
||||
+164
-16
@@ -25,31 +25,107 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Any, Iterable, List, Mapping, Optional, Set
|
||||
|
||||
from typing import List
|
||||
try:
|
||||
import tomllib
|
||||
except ImportError:
|
||||
import tomli as tomllib
|
||||
|
||||
STRICT_LINTS = {"unknown_lints", "warnings"}
|
||||
|
||||
|
||||
def generate_cfg_flags(header: str) -> List[str]:
|
||||
class CargoTOML:
|
||||
tomldata: Mapping[Any, Any]
|
||||
workspace_data: Mapping[Any, Any]
|
||||
check_cfg: Set[str]
|
||||
|
||||
def __init__(self, path: Optional[str], workspace: Optional[str]):
|
||||
if path is not None:
|
||||
with open(path, 'rb') as f:
|
||||
self.tomldata = tomllib.load(f)
|
||||
else:
|
||||
self.tomldata = {"lints": {"workspace": True}}
|
||||
|
||||
if workspace is not None:
|
||||
with open(workspace, 'rb') as f:
|
||||
self.workspace_data = tomllib.load(f)
|
||||
if "workspace" not in self.workspace_data:
|
||||
self.workspace_data["workspace"] = {}
|
||||
|
||||
self.check_cfg = set(self.find_check_cfg())
|
||||
|
||||
def find_check_cfg(self) -> Iterable[str]:
|
||||
toml_lints = self.lints
|
||||
rust_lints = toml_lints.get("rust", {})
|
||||
cfg_lint = rust_lints.get("unexpected_cfgs", {})
|
||||
return cfg_lint.get("check-cfg", [])
|
||||
|
||||
@property
|
||||
def lints(self) -> Mapping[Any, Any]:
|
||||
return self.get_table("lints", True)
|
||||
|
||||
def get_table(self, key: str, can_be_workspace: bool = False) -> Mapping[Any, Any]:
|
||||
table = self.tomldata.get(key, {})
|
||||
if can_be_workspace and table.get("workspace", False) is True:
|
||||
table = self.workspace_data["workspace"].get(key, {})
|
||||
|
||||
return table
|
||||
|
||||
|
||||
@dataclass
|
||||
class LintFlag:
|
||||
flags: List[str]
|
||||
priority: int
|
||||
|
||||
|
||||
def generate_lint_flags(cargo_toml: CargoTOML, strict_lints: bool) -> Iterable[str]:
|
||||
"""Converts Cargo.toml lints to rustc -A/-D/-F/-W flags."""
|
||||
|
||||
toml_lints = cargo_toml.lints
|
||||
|
||||
lint_list = []
|
||||
for k, v in toml_lints.items():
|
||||
prefix = "" if k == "rust" else k + "::"
|
||||
for lint, data in v.items():
|
||||
level = data if isinstance(data, str) else data["level"]
|
||||
priority = 0 if isinstance(data, str) else data.get("priority", 0)
|
||||
if level == "deny":
|
||||
flag = "-D"
|
||||
elif level == "allow":
|
||||
flag = "-A"
|
||||
elif level == "warn":
|
||||
flag = "-W"
|
||||
elif level == "forbid":
|
||||
flag = "-F"
|
||||
else:
|
||||
raise Exception(f"invalid level {level} for {prefix}{lint}")
|
||||
|
||||
if not (strict_lints and lint in STRICT_LINTS):
|
||||
lint_list.append(LintFlag(flags=[flag, prefix + lint], priority=priority))
|
||||
|
||||
if strict_lints:
|
||||
for lint in STRICT_LINTS:
|
||||
lint_list.append(LintFlag(flags=["-D", lint], priority=1000000))
|
||||
|
||||
lint_list.sort(key=lambda x: x.priority)
|
||||
for lint in lint_list:
|
||||
yield from lint.flags
|
||||
|
||||
|
||||
def generate_cfg_flags(header: str, cargo_toml: CargoTOML) -> Iterable[str]:
|
||||
"""Converts defines from config[..].h headers to rustc --cfg flags."""
|
||||
|
||||
def cfg_name(name: str) -> str:
|
||||
"""Filter function for C #defines"""
|
||||
if (
|
||||
name.startswith("CONFIG_")
|
||||
or name.startswith("TARGET_")
|
||||
or name.startswith("HAVE_")
|
||||
):
|
||||
return name
|
||||
return ""
|
||||
|
||||
with open(header, encoding="utf-8") as cfg:
|
||||
config = [l.split()[1:] for l in cfg if l.startswith("#define")]
|
||||
|
||||
cfg_list = []
|
||||
for cfg in config:
|
||||
name = cfg_name(cfg[0])
|
||||
if not name:
|
||||
name = cfg[0]
|
||||
if f'cfg({name})' not in cargo_toml.check_cfg:
|
||||
continue
|
||||
if len(cfg) >= 2 and cfg[1] != "1":
|
||||
continue
|
||||
@@ -59,7 +135,6 @@ def generate_cfg_flags(header: str) -> List[str]:
|
||||
|
||||
|
||||
def main() -> None:
|
||||
# pylint: disable=missing-function-docstring
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-v", "--verbose", action="store_true")
|
||||
parser.add_argument(
|
||||
@@ -71,12 +146,85 @@ def main() -> None:
|
||||
required=False,
|
||||
default=[],
|
||||
)
|
||||
parser.add_argument(
|
||||
metavar="TOML_FILE",
|
||||
action="store",
|
||||
dest="cargo_toml",
|
||||
help="path to Cargo.toml file",
|
||||
nargs='?',
|
||||
)
|
||||
parser.add_argument(
|
||||
"--workspace",
|
||||
metavar="DIR",
|
||||
action="store",
|
||||
dest="workspace",
|
||||
help="path to root of the workspace",
|
||||
required=False,
|
||||
default=None,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--features",
|
||||
action="store_true",
|
||||
dest="features",
|
||||
help="generate --check-cfg arguments for features",
|
||||
required=False,
|
||||
default=None,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--lints",
|
||||
action="store_true",
|
||||
dest="lints",
|
||||
help="generate arguments from [lints] table",
|
||||
required=False,
|
||||
default=None,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--rustc-version",
|
||||
metavar="VERSION",
|
||||
dest="rustc_version",
|
||||
action="store",
|
||||
help="version of rustc",
|
||||
required=False,
|
||||
default="1.0.0",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--strict-lints",
|
||||
action="store_true",
|
||||
dest="strict_lints",
|
||||
help="apply stricter checks (for nightly Rust)",
|
||||
default=False,
|
||||
)
|
||||
args = parser.parse_args()
|
||||
if args.verbose:
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logging.debug("args: %s", args)
|
||||
|
||||
rustc_version = tuple((int(x) for x in args.rustc_version.split('.')[0:2]))
|
||||
if args.workspace:
|
||||
workspace_cargo_toml = Path(args.workspace, "Cargo.toml").resolve()
|
||||
cargo_toml = CargoTOML(args.cargo_toml, str(workspace_cargo_toml))
|
||||
else:
|
||||
cargo_toml = CargoTOML(args.cargo_toml, None)
|
||||
|
||||
if args.lints:
|
||||
for tok in generate_lint_flags(cargo_toml, args.strict_lints):
|
||||
print(tok)
|
||||
|
||||
if rustc_version >= (1, 80):
|
||||
if args.lints:
|
||||
print("--check-cfg")
|
||||
print("cfg(test)")
|
||||
for cfg in sorted(cargo_toml.check_cfg):
|
||||
print("--check-cfg")
|
||||
print(cfg)
|
||||
if args.features:
|
||||
for feature in cargo_toml.get_table("features"):
|
||||
if feature != "default":
|
||||
print("--check-cfg")
|
||||
print(f'cfg(feature,values("{feature}"))')
|
||||
|
||||
for header in args.config_headers:
|
||||
for tok in generate_cfg_flags(header):
|
||||
for tok in generate_cfg_flags(header, cargo_toml):
|
||||
print(tok)
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Command-line wrapper for the tracetool machinery.
|
||||
|
||||
+182
-46
@@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
Machinery for generating tracing-related intermediate files.
|
||||
@@ -12,12 +12,13 @@ __maintainer__ = "Stefan Hajnoczi"
|
||||
__email__ = "stefanha@redhat.com"
|
||||
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import weakref
|
||||
from pathlib import PurePath
|
||||
|
||||
import tracetool.format
|
||||
import tracetool.backend
|
||||
import tracetool.format
|
||||
|
||||
|
||||
def error_write(*lines):
|
||||
@@ -29,6 +30,49 @@ def error(*lines):
|
||||
error_write(*lines)
|
||||
sys.exit(1)
|
||||
|
||||
FMT_TOKEN = re.compile(r'''(?:
|
||||
" ( (?: [^"\\] | \\[\\"abfnrt] | # a string literal
|
||||
\\x[0-9a-fA-F][0-9a-fA-F]) *? ) "
|
||||
| ( PRI [duixX] (?:8|16|32|64|PTR|MAX) ) # a PRIxxx macro
|
||||
| \s+ # spaces (ignored)
|
||||
)''', re.X)
|
||||
|
||||
PRI_SIZE_MAP = {
|
||||
'8': 'hh',
|
||||
'16': 'h',
|
||||
'32': '',
|
||||
'64': 'll',
|
||||
'PTR': 't',
|
||||
'MAX': 'j',
|
||||
}
|
||||
|
||||
def expand_format_string(c_fmt, prefix=""):
|
||||
def pri_macro_to_fmt(pri_macro):
|
||||
assert pri_macro.startswith("PRI")
|
||||
fmt_type = pri_macro[3] # 'd', 'i', 'u', or 'x'
|
||||
fmt_size = pri_macro[4:] # '8', '16', '32', '64', 'PTR', 'MAX'
|
||||
|
||||
size = PRI_SIZE_MAP.get(fmt_size, None)
|
||||
if size is None:
|
||||
raise Exception(f"unknown macro {pri_macro}")
|
||||
return size + fmt_type
|
||||
|
||||
result = prefix
|
||||
pos = 0
|
||||
while pos < len(c_fmt):
|
||||
m = FMT_TOKEN.match(c_fmt, pos)
|
||||
if not m:
|
||||
print("No match at position", pos, ":", repr(c_fmt[pos:]), file=sys.stderr)
|
||||
raise Exception("syntax error in trace file")
|
||||
if m[1]:
|
||||
substr = m[1]
|
||||
elif m[2]:
|
||||
substr = pri_macro_to_fmt(m[2])
|
||||
else:
|
||||
substr = ""
|
||||
result += substr
|
||||
pos = m.end()
|
||||
return result
|
||||
|
||||
out_lineno = 1
|
||||
out_filename = '<none>'
|
||||
@@ -36,7 +80,7 @@ out_fobj = sys.stdout
|
||||
|
||||
def out_open(filename):
|
||||
global out_filename, out_fobj
|
||||
out_filename = filename
|
||||
out_filename = posix_relpath(filename)
|
||||
out_fobj = open(filename, 'wt')
|
||||
|
||||
def out(*lines, **kwargs):
|
||||
@@ -88,6 +132,49 @@ ALLOWED_TYPES = [
|
||||
"ptrdiff_t",
|
||||
]
|
||||
|
||||
C_TYPE_KEYWORDS = {"char", "int", "void", "short", "long", "signed", "unsigned"}
|
||||
|
||||
C_TO_RUST_TYPE_MAP = {
|
||||
"int": "std::ffi::c_int",
|
||||
"long": "std::ffi::c_long",
|
||||
"long long": "std::ffi::c_longlong",
|
||||
"short": "std::ffi::c_short",
|
||||
"char": "std::ffi::c_char",
|
||||
"bool": "bool",
|
||||
"unsigned": "std::ffi::c_uint",
|
||||
# multiple keywords, keep them sorted
|
||||
"long unsigned": "std::ffi::c_long",
|
||||
"long long unsigned": "std::ffi::c_ulonglong",
|
||||
"short unsigned": "std::ffi::c_ushort",
|
||||
"char unsigned": "u8",
|
||||
"int8_t": "i8",
|
||||
"uint8_t": "u8",
|
||||
"int16_t": "i16",
|
||||
"uint16_t": "u16",
|
||||
"int32_t": "i32",
|
||||
"uint32_t": "u32",
|
||||
"int64_t": "i64",
|
||||
"uint64_t": "u64",
|
||||
"void": "()",
|
||||
"size_t": "usize",
|
||||
"ssize_t": "isize",
|
||||
"uintptr_t": "usize",
|
||||
"ptrdiff_t": "isize",
|
||||
}
|
||||
|
||||
# Rust requires manual casting of <32-bit types when passing them to
|
||||
# variable-argument functions.
|
||||
RUST_VARARGS_SMALL_TYPES = {
|
||||
"std::ffi::c_short",
|
||||
"std::ffi::c_ushort",
|
||||
"std::ffi::c_char",
|
||||
"i8",
|
||||
"u8",
|
||||
"i16",
|
||||
"u16",
|
||||
"bool",
|
||||
}
|
||||
|
||||
def validate_type(name):
|
||||
bits = name.split(" ")
|
||||
for bit in bits:
|
||||
@@ -103,6 +190,38 @@ def validate_type(name):
|
||||
"other complex pointer types should be "
|
||||
"declared as 'void *'" % name)
|
||||
|
||||
def c_type_to_rust(name):
|
||||
ptr = False
|
||||
const = False
|
||||
name = name.rstrip()
|
||||
if name[-1] == '*':
|
||||
name = name[:-1].rstrip()
|
||||
ptr = True
|
||||
if name[-1] == '*':
|
||||
# pointers to pointers are the same as void*
|
||||
name = "void"
|
||||
|
||||
bits = name.split()
|
||||
if "const" in bits:
|
||||
const = True
|
||||
bits.remove("const")
|
||||
if bits[0] in C_TYPE_KEYWORDS:
|
||||
if "signed" in bits:
|
||||
bits.remove("signed")
|
||||
if len(bits) > 1 and "int" in bits:
|
||||
bits.remove("int")
|
||||
bits.sort()
|
||||
name = ' '.join(bits)
|
||||
else:
|
||||
if len(bits) > 1:
|
||||
raise ValueError("Invalid type '%s'." % name)
|
||||
name = bits[0]
|
||||
|
||||
ty = C_TO_RUST_TYPE_MAP[name.strip()]
|
||||
if ptr:
|
||||
ty = f'*{"const" if const else "mut"} {ty}'
|
||||
return ty
|
||||
|
||||
class Arguments:
|
||||
"""Event arguments description."""
|
||||
|
||||
@@ -120,10 +239,6 @@ class Arguments:
|
||||
else:
|
||||
self._args.append(arg)
|
||||
|
||||
def copy(self):
|
||||
"""Create a new copy."""
|
||||
return Arguments(list(self._args))
|
||||
|
||||
@staticmethod
|
||||
def build(arg_str):
|
||||
"""Build and Arguments instance from an argument string.
|
||||
@@ -168,10 +283,16 @@ class Arguments:
|
||||
|
||||
def __str__(self):
|
||||
"""String suitable for declaring function arguments."""
|
||||
def onearg(t, n):
|
||||
if t[-1] == '*':
|
||||
return "".join([t, n])
|
||||
else:
|
||||
return " ".join([t, n])
|
||||
|
||||
if len(self._args) == 0:
|
||||
return "void"
|
||||
else:
|
||||
return ", ".join([ " ".join([t, n]) for t,n in self._args ])
|
||||
return ", ".join([ onearg(t, n) for t,n in self._args ])
|
||||
|
||||
def __repr__(self):
|
||||
"""Evaluable string representation for this object."""
|
||||
@@ -189,6 +310,43 @@ class Arguments:
|
||||
"""List of argument names casted to their type."""
|
||||
return ["(%s)%s" % (type_, name) for type_, name in self._args]
|
||||
|
||||
def rust_decl_extern(self):
|
||||
"""Return a Rust argument list for an extern "C" function"""
|
||||
return ", ".join((f"_{name}: {c_type_to_rust(type_)}"
|
||||
for type_, name in self._args))
|
||||
|
||||
def rust_decl(self):
|
||||
"""Return a Rust argument list for a tracepoint function"""
|
||||
def decl_type(type_):
|
||||
if type_ == "const char *":
|
||||
return "&std::ffi::CStr"
|
||||
return c_type_to_rust(type_)
|
||||
|
||||
return ", ".join((f"_{name}: {decl_type(type_)}"
|
||||
for type_, name in self._args))
|
||||
|
||||
def rust_call_extern(self):
|
||||
"""Return a Rust argument list for a call to an extern "C" function"""
|
||||
def rust_cast(name, type_):
|
||||
if type_ == "const char *":
|
||||
return f"_{name}.as_ptr()"
|
||||
return f"_{name}"
|
||||
|
||||
return ", ".join((rust_cast(name, type_) for type_, name in self._args))
|
||||
|
||||
def rust_call_varargs(self):
|
||||
"""Return a Rust argument list for a call to a C varargs function"""
|
||||
def rust_cast(name, type_):
|
||||
if type_ == "const char *":
|
||||
return f"_{name}.as_ptr()"
|
||||
|
||||
type_ = c_type_to_rust(type_)
|
||||
if type_ in RUST_VARARGS_SMALL_TYPES:
|
||||
return f"_{name} as std::ffi::c_int"
|
||||
return f"_{name} /* as {type_} */"
|
||||
|
||||
return ", ".join((rust_cast(name, type_) for type_, name in self._args))
|
||||
|
||||
|
||||
class Event(object):
|
||||
"""Event description.
|
||||
@@ -214,13 +372,12 @@ class Event(object):
|
||||
r"(?P<name>\w+)"
|
||||
r"\((?P<args>[^)]*)\)"
|
||||
r"\s*"
|
||||
r"(?:(?:(?P<fmt_trans>\".+),)?\s*(?P<fmt>\".+))?"
|
||||
r"(?P<fmt>\".+)?"
|
||||
r"\s*")
|
||||
|
||||
_VALID_PROPS = set(["disable", "vcpu"])
|
||||
_VALID_PROPS = set(["disable"])
|
||||
|
||||
def __init__(self, name, props, fmt, args, lineno, filename, orig=None,
|
||||
event_trans=None, event_exec=None):
|
||||
def __init__(self, name, props, fmt, args, lineno, filename):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
@@ -228,20 +385,14 @@ class Event(object):
|
||||
Event name.
|
||||
props : list of str
|
||||
Property names.
|
||||
fmt : str, list of str
|
||||
Event printing format string(s).
|
||||
fmt : str
|
||||
Event printing format string.
|
||||
args : Arguments
|
||||
Event arguments.
|
||||
lineno : int
|
||||
The line number in the input file.
|
||||
filename : str
|
||||
The path to the input file.
|
||||
orig : Event or None
|
||||
Original Event before transformation/generation.
|
||||
event_trans : Event or None
|
||||
Generated translation-time event ("tcg" property).
|
||||
event_exec : Event or None
|
||||
Generated execution-time event ("tcg" property).
|
||||
|
||||
"""
|
||||
self.name = name
|
||||
@@ -250,29 +401,16 @@ class Event(object):
|
||||
self.args = args
|
||||
self.lineno = int(lineno)
|
||||
self.filename = str(filename)
|
||||
self.event_trans = event_trans
|
||||
self.event_exec = event_exec
|
||||
|
||||
if len(args) > 10:
|
||||
raise ValueError("Event '%s' has more than maximum permitted "
|
||||
"argument count" % name)
|
||||
|
||||
if orig is None:
|
||||
self.original = weakref.ref(self)
|
||||
else:
|
||||
self.original = orig
|
||||
|
||||
unknown_props = set(self.properties) - self._VALID_PROPS
|
||||
if len(unknown_props) > 0:
|
||||
raise ValueError("Unknown properties: %s"
|
||||
% ", ".join(unknown_props))
|
||||
assert isinstance(self.fmt, str) or len(self.fmt) == 2
|
||||
|
||||
def copy(self):
|
||||
"""Create a new copy."""
|
||||
return Event(self.name, list(self.properties), self.fmt,
|
||||
self.args.copy(), self.lineno, self.filename,
|
||||
self, self.event_trans, self.event_exec)
|
||||
|
||||
@staticmethod
|
||||
def build(line_str, lineno, filename):
|
||||
@@ -294,8 +432,7 @@ class Event(object):
|
||||
name = groups["name"]
|
||||
props = groups["props"].split()
|
||||
fmt = groups["fmt"]
|
||||
fmt_trans = groups["fmt_trans"]
|
||||
if fmt.find("%m") != -1 or fmt_trans.find("%m") != -1:
|
||||
if fmt.find("%m") != -1:
|
||||
raise ValueError("Event format '%m' is forbidden, pass the error "
|
||||
"as an explicit trace argument")
|
||||
if fmt.endswith(r'\n"'):
|
||||
@@ -304,33 +441,25 @@ class Event(object):
|
||||
if '\\n' in fmt:
|
||||
raise ValueError("Event format must not use new line character")
|
||||
|
||||
if len(fmt_trans) > 0:
|
||||
fmt = [fmt_trans, fmt]
|
||||
args = Arguments.build(groups["args"])
|
||||
|
||||
return Event(name, props, fmt, args, lineno, filename)
|
||||
return Event(name, props, fmt, args, lineno, posix_relpath(filename))
|
||||
|
||||
def __repr__(self):
|
||||
"""Evaluable string representation for this object."""
|
||||
if isinstance(self.fmt, str):
|
||||
fmt = self.fmt
|
||||
else:
|
||||
fmt = "%s, %s" % (self.fmt[0], self.fmt[1])
|
||||
return "Event('%s %s(%s) %s')" % (" ".join(self.properties),
|
||||
self.name,
|
||||
self.args,
|
||||
fmt)
|
||||
self.fmt)
|
||||
# Star matching on PRI is dangerous as one might have multiple
|
||||
# arguments with that format, hence the non-greedy version of it.
|
||||
_FMT = re.compile(r"(%[\d\.]*\w+|%.*?PRI\S+)")
|
||||
|
||||
def formats(self):
|
||||
"""List conversion specifiers in the argument print format string."""
|
||||
assert not isinstance(self.fmt, list)
|
||||
return self._FMT.findall(self.fmt)
|
||||
|
||||
QEMU_TRACE = "trace_%(name)s"
|
||||
QEMU_TRACE_NOCHECK = "_nocheck__" + QEMU_TRACE
|
||||
QEMU_TRACE_TCG = QEMU_TRACE + "_tcg"
|
||||
QEMU_DSTATE = "_TRACE_%(NAME)s_DSTATE"
|
||||
QEMU_BACKEND_DSTATE = "TRACE_%(NAME)s_BACKEND_DSTATE"
|
||||
@@ -447,3 +576,10 @@ def generate(events, group, format, backends,
|
||||
tracetool.backend.dtrace.PROBEPREFIX = probe_prefix
|
||||
|
||||
tracetool.format.generate(events, format, backend, group)
|
||||
|
||||
def posix_relpath(path, start=None):
|
||||
try:
|
||||
path = os.path.relpath(path, start)
|
||||
except ValueError:
|
||||
pass
|
||||
return PurePath(path).as_posix()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
Backend management.
|
||||
@@ -19,11 +19,15 @@ All backends must generate their contents through the 'tracetool.out' routine.
|
||||
Backend attributes
|
||||
------------------
|
||||
|
||||
========= ====================================================================
|
||||
Attribute Description
|
||||
========= ====================================================================
|
||||
PUBLIC If exists and is set to 'True', the backend is considered "public".
|
||||
========= ====================================================================
|
||||
=========================== ====================================================
|
||||
Attribute Description
|
||||
=========================== ====================================================
|
||||
PUBLIC If exists and is set to 'True', the backend is
|
||||
considered "public".
|
||||
CHECK_TRACE_EVENT_GET_STATE If exists and is set to 'True', the backend-specific
|
||||
code inside the tracepoint is emitted within an
|
||||
``if trace_event_get_state()`` conditional.
|
||||
=========================== ====================================================
|
||||
|
||||
|
||||
Backend functions
|
||||
@@ -94,29 +98,40 @@ def exists(name):
|
||||
if name == "nop":
|
||||
return True
|
||||
name = name.replace("-", "_")
|
||||
return tracetool.try_import("tracetool.backend." + name)[1]
|
||||
return tracetool.try_import("tracetool.backend." + name)[0]
|
||||
|
||||
|
||||
class Wrapper:
|
||||
def __init__(self, backends, format):
|
||||
self._backends = [backend.replace("-", "_") for backend in backends]
|
||||
self._format = format.replace("-", "_")
|
||||
self.check_trace_event_get_state = False
|
||||
for backend in self._backends:
|
||||
assert exists(backend)
|
||||
assert tracetool.format.exists(self._format)
|
||||
for backend in self.backend_modules():
|
||||
check_trace_event_get_state = getattr(backend, "CHECK_TRACE_EVENT_GET_STATE", False)
|
||||
self.check_trace_event_get_state = self.check_trace_event_get_state or check_trace_event_get_state
|
||||
|
||||
def _run_function(self, name, *args, **kwargs):
|
||||
def backend_modules(self):
|
||||
for backend in self._backends:
|
||||
func = tracetool.try_import("tracetool.backend." + backend,
|
||||
name % self._format, None)[1]
|
||||
if func is not None:
|
||||
func(*args, **kwargs)
|
||||
module = tracetool.try_import("tracetool.backend." + backend)[1]
|
||||
if module is not None:
|
||||
yield module
|
||||
|
||||
def _run_function(self, name, *args, check_trace_event_get_state=None, **kwargs):
|
||||
for backend in self.backend_modules():
|
||||
func = getattr(backend, name % self._format, None)
|
||||
if func is not None and \
|
||||
(check_trace_event_get_state is None or
|
||||
check_trace_event_get_state == getattr(backend, 'CHECK_TRACE_EVENT_GET_STATE', False)):
|
||||
func(*args, **kwargs)
|
||||
|
||||
def generate_begin(self, events, group):
|
||||
self._run_function("generate_%s_begin", events, group)
|
||||
|
||||
def generate(self, event, group):
|
||||
self._run_function("generate_%s", event, group)
|
||||
def generate(self, event, group, check_trace_event_get_state=None):
|
||||
self._run_function("generate_%s", event, group, check_trace_event_get_state=check_trace_event_get_state)
|
||||
|
||||
def generate_backend_dstate(self, event, group):
|
||||
self._run_function("generate_%s_backend_dstate", event, group)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
DTrace/SystemTAP backend.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
Ftrace built-in backend.
|
||||
@@ -12,12 +12,11 @@ __maintainer__ = "Stefan Hajnoczi"
|
||||
__email__ = "stefanha@redhat.com"
|
||||
|
||||
|
||||
import os.path
|
||||
|
||||
from tracetool import out
|
||||
from tracetool import out, expand_format_string
|
||||
|
||||
|
||||
PUBLIC = True
|
||||
CHECK_TRACE_EVENT_GET_STATE = True
|
||||
|
||||
|
||||
def generate_h_begin(events, group):
|
||||
@@ -30,24 +29,13 @@ def generate_h(event, group):
|
||||
if len(event.args) > 0:
|
||||
argnames = ", " + argnames
|
||||
|
||||
out(' {',
|
||||
' char ftrace_buf[MAX_TRACE_STRLEN];',
|
||||
' int unused __attribute__ ((unused));',
|
||||
' int trlen;',
|
||||
' if (trace_event_get_state(%(event_id)s)) {',
|
||||
'#line %(event_lineno)d "%(event_filename)s"',
|
||||
' trlen = snprintf(ftrace_buf, MAX_TRACE_STRLEN,',
|
||||
' "%(name)s " %(fmt)s "\\n" %(argnames)s);',
|
||||
out('#line %(event_lineno)d "%(event_filename)s"',
|
||||
' ftrace_write("%(name)s " %(fmt)s "\\n" %(argnames)s);',
|
||||
'#line %(out_next_lineno)d "%(out_filename)s"',
|
||||
' trlen = MIN(trlen, MAX_TRACE_STRLEN - 1);',
|
||||
' unused = write(trace_marker_fd, ftrace_buf, trlen);',
|
||||
' }',
|
||||
' }',
|
||||
name=event.name,
|
||||
args=event.args,
|
||||
event_id="TRACE_" + event.name.upper(),
|
||||
event_lineno=event.lineno,
|
||||
event_filename=os.path.relpath(event.filename),
|
||||
event_filename=event.filename,
|
||||
fmt=event.fmt.rstrip("\n"),
|
||||
argnames=argnames)
|
||||
|
||||
@@ -55,3 +43,9 @@ def generate_h(event, group):
|
||||
def generate_h_backend_dstate(event, group):
|
||||
out(' trace_event_get_state_dynamic_by_id(%(event_id)s) || \\',
|
||||
event_id="TRACE_" + event.name.upper())
|
||||
|
||||
def generate_rs(event, group):
|
||||
out(' let format_string = c"%(fmt)s";',
|
||||
' unsafe {bindings::ftrace_write(format_string.as_ptr() as *const c_char, %(args)s);}',
|
||||
fmt=expand_format_string(event.fmt),
|
||||
args=event.args.rust_call_varargs())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
Stderr built-in backend.
|
||||
@@ -12,17 +12,15 @@ __maintainer__ = "Stefan Hajnoczi"
|
||||
__email__ = "stefanha@redhat.com"
|
||||
|
||||
|
||||
import os.path
|
||||
|
||||
from tracetool import out
|
||||
from tracetool import out, expand_format_string
|
||||
|
||||
|
||||
PUBLIC = True
|
||||
CHECK_TRACE_EVENT_GET_STATE = True
|
||||
|
||||
|
||||
def generate_h_begin(events, group):
|
||||
out('#include "qemu/log-for-trace.h"',
|
||||
'#include "qemu/error-report.h"',
|
||||
'')
|
||||
|
||||
|
||||
@@ -31,31 +29,13 @@ def generate_h(event, group):
|
||||
if len(event.args) > 0:
|
||||
argnames = ", " + argnames
|
||||
|
||||
if "vcpu" in event.properties:
|
||||
# already checked on the generic format code
|
||||
cond = "true"
|
||||
else:
|
||||
cond = "trace_event_get_state(%s)" % ("TRACE_" + event.name.upper())
|
||||
|
||||
out(' if (%(cond)s && qemu_loglevel_mask(LOG_TRACE)) {',
|
||||
' if (message_with_timestamp) {',
|
||||
' struct timeval _now;',
|
||||
' gettimeofday(&_now, NULL);',
|
||||
'#line %(event_lineno)d "%(event_filename)s"',
|
||||
' qemu_log("%%d@%%zu.%%06zu:%(name)s " %(fmt)s "\\n",',
|
||||
' qemu_get_thread_id(),',
|
||||
' (size_t)_now.tv_sec, (size_t)_now.tv_usec',
|
||||
' %(argnames)s);',
|
||||
'#line %(out_next_lineno)d "%(out_filename)s"',
|
||||
' } else {',
|
||||
out(' if (qemu_loglevel_mask(LOG_TRACE)) {',
|
||||
'#line %(event_lineno)d "%(event_filename)s"',
|
||||
' qemu_log("%(name)s " %(fmt)s "\\n"%(argnames)s);',
|
||||
'#line %(out_next_lineno)d "%(out_filename)s"',
|
||||
' }',
|
||||
' }',
|
||||
cond=cond,
|
||||
event_lineno=event.lineno,
|
||||
event_filename=os.path.relpath(event.filename),
|
||||
event_filename=event.filename,
|
||||
name=event.name,
|
||||
fmt=event.fmt.rstrip("\n"),
|
||||
argnames=argnames)
|
||||
@@ -64,3 +44,11 @@ def generate_h(event, group):
|
||||
def generate_h_backend_dstate(event, group):
|
||||
out(' trace_event_get_state_dynamic_by_id(%(event_id)s) || \\',
|
||||
event_id="TRACE_" + event.name.upper())
|
||||
|
||||
def generate_rs(event, group):
|
||||
out(' let format_string = c"%(fmt)s\\n";',
|
||||
' if (unsafe { bindings::qemu_loglevel } & bindings::LOG_TRACE) != 0 {',
|
||||
' unsafe { bindings::qemu_log(format_string.as_ptr() as *const c_char, %(args)s);}',
|
||||
' }',
|
||||
fmt=expand_format_string(event.fmt, event.name + " "),
|
||||
args=event.args.rust_call_varargs())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
Simple built-in backend.
|
||||
@@ -16,6 +16,7 @@ from tracetool import out
|
||||
|
||||
|
||||
PUBLIC = True
|
||||
CHECK_TRACE_EVENT_GET_STATE = True
|
||||
|
||||
|
||||
def is_string(arg):
|
||||
@@ -36,7 +37,7 @@ def generate_h_begin(events, group):
|
||||
|
||||
|
||||
def generate_h(event, group):
|
||||
out(' _simple_%(api)s(%(args)s);',
|
||||
out(' _simple_%(api)s(%(args)s);',
|
||||
api=event.api(),
|
||||
args=", ".join(event.args.names()))
|
||||
|
||||
@@ -72,22 +73,10 @@ def generate_c(event, group):
|
||||
if len(event.args) == 0:
|
||||
sizestr = '0'
|
||||
|
||||
event_id = 'TRACE_' + event.name.upper()
|
||||
if "vcpu" in event.properties:
|
||||
# already checked on the generic format code
|
||||
cond = "true"
|
||||
else:
|
||||
cond = "trace_event_get_state(%s)" % event_id
|
||||
|
||||
out('',
|
||||
' if (!%(cond)s) {',
|
||||
' return;',
|
||||
' }',
|
||||
'',
|
||||
' if (trace_record_start(&rec, %(event_obj)s.id, %(size_str)s)) {',
|
||||
' return; /* Trace Buffer Full, Event Dropped ! */',
|
||||
' }',
|
||||
cond=cond,
|
||||
event_obj=event.api(event.QEMU_EVENT),
|
||||
size_str=sizestr)
|
||||
|
||||
@@ -109,3 +98,10 @@ def generate_c(event, group):
|
||||
out(' trace_record_finish(&rec);',
|
||||
'}',
|
||||
'')
|
||||
|
||||
def generate_rs(event, group):
|
||||
out(' extern "C" { fn _simple_%(api)s(%(rust_args)s); }',
|
||||
' unsafe { _simple_%(api)s(%(args)s); }',
|
||||
api=event.api(),
|
||||
rust_args=event.args.rust_decl_extern(),
|
||||
args=event.args.rust_call_extern())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
Syslog built-in backend.
|
||||
@@ -12,12 +12,11 @@ __maintainer__ = "Stefan Hajnoczi"
|
||||
__email__ = "stefanha@redhat.com"
|
||||
|
||||
|
||||
import os.path
|
||||
|
||||
from tracetool import out
|
||||
from tracetool import out, expand_format_string
|
||||
|
||||
|
||||
PUBLIC = True
|
||||
CHECK_TRACE_EVENT_GET_STATE = True
|
||||
|
||||
|
||||
def generate_h_begin(events, group):
|
||||
@@ -30,24 +29,20 @@ def generate_h(event, group):
|
||||
if len(event.args) > 0:
|
||||
argnames = ", " + argnames
|
||||
|
||||
if "vcpu" in event.properties:
|
||||
# already checked on the generic format code
|
||||
cond = "true"
|
||||
else:
|
||||
cond = "trace_event_get_state(%s)" % ("TRACE_" + event.name.upper())
|
||||
|
||||
out(' if (%(cond)s) {',
|
||||
'#line %(event_lineno)d "%(event_filename)s"',
|
||||
out('#line %(event_lineno)d "%(event_filename)s"',
|
||||
' syslog(LOG_INFO, "%(name)s " %(fmt)s %(argnames)s);',
|
||||
'#line %(out_next_lineno)d "%(out_filename)s"',
|
||||
' }',
|
||||
cond=cond,
|
||||
event_lineno=event.lineno,
|
||||
event_filename=os.path.relpath(event.filename),
|
||||
event_filename=event.filename,
|
||||
name=event.name,
|
||||
fmt=event.fmt.rstrip("\n"),
|
||||
argnames=argnames)
|
||||
|
||||
def generate_rs(event, group):
|
||||
out(' let format_string = c"%(fmt)s";',
|
||||
' unsafe {::trace::syslog(::trace::LOG_INFO, format_string.as_ptr() as *const c_char, %(args)s);}',
|
||||
fmt=expand_format_string(event.fmt),
|
||||
args=event.args.rust_call_varargs())
|
||||
|
||||
def generate_h_backend_dstate(event, group):
|
||||
out(' trace_event_get_state_dynamic_by_id(%(event_id)s) || \\',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
LTTng User Space Tracing backend.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
Format management.
|
||||
@@ -70,7 +70,7 @@ def exists(name):
|
||||
if len(name) == 0:
|
||||
return False
|
||||
name = name.replace("-", "_")
|
||||
return tracetool.try_import("tracetool.format." + name)[1]
|
||||
return tracetool.try_import("tracetool.format." + name)[0]
|
||||
|
||||
|
||||
def generate(events, format, backend, group):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
trace/generated-tracers.c
|
||||
@@ -22,6 +22,7 @@ def generate(events, backend, group):
|
||||
header = "trace-" + group + ".h"
|
||||
|
||||
out('/* This file is autogenerated by tracetool, do not edit. */',
|
||||
'/* SPDX-License-Identifier: GPL-2.0-or-later */',
|
||||
'',
|
||||
'#include "qemu/osdep.h"',
|
||||
'#include "qemu/module.h"',
|
||||
@@ -36,7 +37,7 @@ def generate(events, backend, group):
|
||||
' .id = 0,',
|
||||
' .name = \"%(name)s\",',
|
||||
' .sstate = %(sstate)s,',
|
||||
' .dstate = &%(dstate)s ',
|
||||
' .dstate = &%(dstate)s',
|
||||
'};',
|
||||
event = e.api(e.QEMU_EVENT),
|
||||
name = e.name,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
trace/generated-tracers.dtrace (DTrace only).
|
||||
@@ -39,7 +39,8 @@ def generate(events, backend, group):
|
||||
if not events and platform != "darwin":
|
||||
return
|
||||
|
||||
out('/* This file is autogenerated by tracetool, do not edit. */'
|
||||
out('/* This file is autogenerated by tracetool, do not edit. */',
|
||||
'/* SPDX-License-Identifier: GPL-2.0-or-later */',
|
||||
'',
|
||||
'provider qemu {')
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
trace/generated-tracers.h
|
||||
@@ -19,6 +19,7 @@ def generate(events, backend, group):
|
||||
header = "trace/control.h"
|
||||
|
||||
out('/* This file is autogenerated by tracetool, do not edit. */',
|
||||
'/* SPDX-License-Identifier: GPL-2.0-or-later */',
|
||||
'',
|
||||
'#ifndef TRACE_%s_GENERATED_TRACERS_H' % group.upper(),
|
||||
'#define TRACE_%s_GENERATED_TRACERS_H' % group.upper(),
|
||||
@@ -39,11 +40,6 @@ def generate(events, backend, group):
|
||||
enabled = 0
|
||||
else:
|
||||
enabled = 1
|
||||
if "tcg-exec" in e.properties:
|
||||
# a single define for the two "sub-events"
|
||||
out('#define TRACE_%(name)s_ENABLED %(enabled)d',
|
||||
name=e.original.name.upper(),
|
||||
enabled=enabled)
|
||||
out('#define TRACE_%s_ENABLED %d' % (e.name.upper(), enabled))
|
||||
|
||||
backend.generate_begin(events, group)
|
||||
@@ -59,33 +55,24 @@ def generate(events, backend, group):
|
||||
|
||||
out(' false)')
|
||||
|
||||
# tracer without checks
|
||||
out('',
|
||||
'static inline void %(api)s(%(args)s)',
|
||||
'{',
|
||||
api=e.api(e.QEMU_TRACE_NOCHECK),
|
||||
api=e.api(),
|
||||
args=e.args)
|
||||
|
||||
if "disable" not in e.properties:
|
||||
backend.generate(e, group)
|
||||
backend.generate(e, group, check_trace_event_get_state=False)
|
||||
|
||||
if backend.check_trace_event_get_state:
|
||||
event_id = 'TRACE_' + e.name.upper()
|
||||
cond = "trace_event_get_state(%s)" % event_id
|
||||
out(' if (%(cond)s) {',
|
||||
cond=cond)
|
||||
backend.generate(e, group, check_trace_event_get_state=True)
|
||||
out(' }')
|
||||
out('}')
|
||||
|
||||
cond = "true"
|
||||
|
||||
out('',
|
||||
'static inline void %(api)s(%(args)s)',
|
||||
'{',
|
||||
' if (%(cond)s) {',
|
||||
' %(api_nocheck)s(%(names)s);',
|
||||
' }',
|
||||
'}',
|
||||
api=e.api(),
|
||||
api_nocheck=e.api(e.QEMU_TRACE_NOCHECK),
|
||||
args=e.args,
|
||||
names=", ".join(e.args.names()),
|
||||
cond=cond)
|
||||
|
||||
backend.generate_end(events, group)
|
||||
|
||||
out('#endif /* TRACE_%s_GENERATED_TRACERS_H */' % group.upper())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
Generate .stp file that printfs log messages (DTrace with SystemTAP only).
|
||||
@@ -18,8 +18,6 @@ from tracetool.backend.dtrace import binary, probeprefix
|
||||
from tracetool.backend.simple import is_string
|
||||
from tracetool.format.stap import stap_escape
|
||||
|
||||
def global_var_name(name):
|
||||
return probeprefix().replace(".", "_") + "_" + name
|
||||
|
||||
STATE_SKIP = 0
|
||||
STATE_LITERAL = 1
|
||||
@@ -88,6 +86,7 @@ def c_fmt_to_stap(fmt):
|
||||
|
||||
def generate(events, backend, group):
|
||||
out('/* This file is autogenerated by tracetool, do not edit. */',
|
||||
'/* SPDX-License-Identifier: GPL-2.0-or-later */',
|
||||
'')
|
||||
|
||||
for event_id, e in enumerate(events):
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
trace-DIR.rs
|
||||
"""
|
||||
|
||||
__author__ = "Tanish Desai <tanishdesai37@gmail.com>"
|
||||
__copyright__ = "Copyright 2025, Tanish Desai <tanishdesai37@gmail.com>"
|
||||
__license__ = "GPL version 2 or (at your option) any later version"
|
||||
|
||||
__maintainer__ = "Stefan Hajnoczi"
|
||||
__email__ = "stefanha@redhat.com"
|
||||
|
||||
|
||||
from tracetool import out
|
||||
|
||||
|
||||
def generate(events, backend, group):
|
||||
out('// SPDX-License-Identifier: GPL-2.0-or-later',
|
||||
'// This file is @generated by tracetool, do not edit.',
|
||||
'',
|
||||
'#[allow(unused_imports)]',
|
||||
'use std::ffi::c_char;',
|
||||
'#[allow(unused_imports)]',
|
||||
'use util::bindings;',
|
||||
'',
|
||||
'#[inline(always)]',
|
||||
'fn trace_event_state_is_enabled(dstate: u16) -> bool {',
|
||||
' (unsafe { trace_events_enabled_count }) != 0 && dstate != 0',
|
||||
'}',
|
||||
'',
|
||||
'extern "C" {',
|
||||
' static mut trace_events_enabled_count: u32;',
|
||||
'}',)
|
||||
|
||||
out('extern "C" {')
|
||||
|
||||
for e in events:
|
||||
out(' static mut %s: u16;' % e.api(e.QEMU_DSTATE))
|
||||
out('}')
|
||||
|
||||
backend.generate_begin(events, group)
|
||||
|
||||
for e in events:
|
||||
out('',
|
||||
'#[inline(always)]',
|
||||
'#[allow(dead_code)]',
|
||||
'pub fn %(api)s(%(args)s)',
|
||||
'{',
|
||||
api=e.api(e.QEMU_TRACE),
|
||||
args=e.args.rust_decl())
|
||||
|
||||
if "disable" not in e.properties:
|
||||
backend.generate(e, group, check_trace_event_get_state=False)
|
||||
if backend.check_trace_event_get_state:
|
||||
event_id = 'TRACE_' + e.name.upper()
|
||||
out(' if trace_event_state_is_enabled(unsafe { _%(event_id)s_DSTATE}) {',
|
||||
event_id = event_id,
|
||||
api=e.api())
|
||||
backend.generate(e, group, check_trace_event_get_state=True)
|
||||
out(' }')
|
||||
out('}')
|
||||
|
||||
backend.generate_end(events, group)
|
||||
@@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
Generate .stp file that outputs simpletrace binary traces (DTrace with SystemTAP only).
|
||||
@@ -17,11 +17,10 @@ from tracetool.backend.dtrace import probeprefix
|
||||
from tracetool.backend.simple import is_string
|
||||
from tracetool.format.stap import stap_escape
|
||||
|
||||
def global_var_name(name):
|
||||
return probeprefix().replace(".", "_") + "_" + name
|
||||
|
||||
def generate(events, backend, group):
|
||||
out('/* This file is autogenerated by tracetool, do not edit. */',
|
||||
'/* SPDX-License-Identifier: GPL-2.0-or-later */',
|
||||
'')
|
||||
|
||||
for event_id, e in enumerate(events):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
Generate .stp file (DTrace with SystemTAP only).
|
||||
@@ -38,6 +38,7 @@ def generate(events, backend, group):
|
||||
if "disable" not in e.properties]
|
||||
|
||||
out('/* This file is autogenerated by tracetool, do not edit. */',
|
||||
'/* SPDX-License-Identifier: GPL-2.0-or-later */',
|
||||
'')
|
||||
|
||||
for e in events:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
trace/generated-ust.c
|
||||
@@ -20,6 +20,7 @@ def generate(events, backend, group):
|
||||
if "disabled" not in e.properties]
|
||||
|
||||
out('/* This file is autogenerated by tracetool, do not edit. */',
|
||||
'/* SPDX-License-Identifier: GPL-2.0-or-later */',
|
||||
'',
|
||||
'#include "qemu/osdep.h"',
|
||||
'',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
trace/generated-ust-provider.h
|
||||
@@ -25,6 +25,7 @@ def generate(events, backend, group):
|
||||
include = "trace-ust.h"
|
||||
|
||||
out('/* This file is autogenerated by tracetool, do not edit. */',
|
||||
'/* SPDX-License-Identifier: GPL-2.0-or-later */',
|
||||
'',
|
||||
'#undef TRACEPOINT_PROVIDER',
|
||||
'#define TRACEPOINT_PROVIDER qemu',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user