On Thu, Aug 8, 2024 at 2:56 PM John Snow <js...@redhat.com> wrote:
>
>
>
> On Mon, Aug 5, 2024 at 4:17 PM Octavian Purdila <ta...@google.com> wrote:
>>
>> From: Stefan Stanacar <stefa...@google.com>
>>
>> From: Stefan Stanacar <stefa...@google.com>
>>
>> The CMSIS System View Description format(CMSIS-SVD) is an XML based
>> description of Arm Cortex-M microcontrollers provided and maintained
>> by sillicon vendors. It includes details such as peripherals registers
>> (down to bitfields), peripheral register block addresses, reset
>> values, etc.
>>
>> This script uses this information to create header files that makes it
>> easier to emulate peripherals.
>>
>> The script can be used to create either peripheral specific headers or
>> board / system specific information. The script generated headers are
>> similar to the SVDConv utility.
>>
>> Peripheral specific headers contains information such as register
>> layout, register names and reset values for registers:
>>
>>   typedef struct {
>>     ...
>>     union {
>>       uint32_t PSELID;              /* 0x00000FF8 Peripheral Select and
>>                                      * Flexcomm module ID */
>>       struct {
>>         uint32_t PERSEL : 3;        /* [2..0] Peripheral Select */
>>         uint32_t LOCK : 1;          /* [3..3] Lock the peripheral select */
>>         uint32_t USARTPRESENT : 1;  /* [4..4] USART present indicator */
>>         uint32_t SPIPRESENT : 1;    /* [5..5] SPI present indicator */
>>         uint32_t I2CPRESENT : 1;    /* [6..6] I2C present indicator */
>>         uint32_t I2SPRESENT : 1;    /* [7..7] I2S Present */
>>         uint32_t : 4;
>>         uint32_t ID : 20;           /* [31..12] Flexcomm ID */
>>       } PSELID_b;
>>     };
>>     ...
>>   } FLEXCOMM_Type;                  /* Size = 4096 (0x1000) */
>>
>>   #define FLEXCOMM_PSELID_PERSEL_Pos (0UL)
>>   #define FLEXCOMM_PSELID_PERSEL_Msk (0x7UL)
>>   #define FLEXCOMM_PSELID_LOCK_Pos (3UL)
>>   #define FLEXCOMM_PSELID_LOCK_Msk (0x8UL)
>>   ...
>>
>>   typedef enum {                /* FLEXCOMM_PSELID_LOCK */
>>     /* Peripheral select can be changed by software. */
>>     FLEXCOMM_PSELID_LOCK_UNLOCKED = 0,
>>     /* Peripheral select is locked and cannot be changed until this
>>      * Flexcomm module or the entire device is reset. */
>>     FLEXCOMM_PSELID_LOCK_LOCKED = 1,
>>   } FLEXCOMM_PSELID_LOCK_Enum;
>>   ...
>>
>>   #define FLEXCOMM_REGISTER_NAMES_ARRAY(_name) \
>>     const char *_name[sizeof(FLEXCOMM_Type)] = { \
>>         [4088 ... 4091] = "PSELID", \
>>         [4092 ... 4095] = "PID", \
>>     }
>>
>> Board specific headers contains information about peripheral base
>> register addresses.
>>
>> Signed-off-by: Stefan Stanacar <stefa...@google.com>
>> Signed-off-by: Octavian Purdila <ta...@google.com>
>> ---
>>  configure                 |   2 +-
>>  meson.build               |   4 +
>>  python/setup.cfg          |   1 +
>>  python/tests/minreqs.txt  |   3 +
>>  pythondeps.toml           |   3 +
>>  scripts/svd-gen-header.py | 342 ++++++++++++++++++++++++++++++++++++++
>>  6 files changed, 354 insertions(+), 1 deletion(-)
>>  create mode 100755 scripts/svd-gen-header.py
>>
>> diff --git a/configure b/configure
>> index 5ad1674ca5..811bfa5d54 100755
>> --- a/configure
>> +++ b/configure
>> @@ -956,7 +956,7 @@ mkvenv="$python ${source_path}/python/scripts/mkvenv.py"
>>  # Finish preparing the virtual environment using vendored .whl files
>>
>>  $mkvenv ensuregroup --dir "${source_path}/python/wheels" \
>> -     ${source_path}/pythondeps.toml meson || exit 1
>> +     ${source_path}/pythondeps.toml meson svd-gen-header || exit 1
>

Hi John,

Thanks for reviewing!

>
> I haven't read the rest of this series; I'm chiming in solely from the 
> build/python maintainer angle. Do we *always* need pysvd, no matter how QEMU 
> was configured? Adding it to the meson line here is a very big hammer.
>

I think the minimum we can do is to install it only if CONFIG_ARM is
enabled. That might change in the future if the models we create with
pysvd are enabled for other architectures.

> If not, consider looking at how sphinx (the "docs" group) is only 
> conditionally installed into the configure venv and mimic that using the 
> appropriate configure flags that necessitate the availability of pyvsd for 
> the QEMU build.

Thanks for the pointer, I'll take a look.

>
> We also need to provide a way for pysvd to be available offline; some 
> packages are available via distro libs and if this package is available for 
> every distro we officially support, that's sufficient (but requires updates 
> to our various docker and VM test configuration files to add the new 
> dependency). Otherwise, like we do for meson, we need to vendor the wheel in 
> the tree so offline tarball builds will continue to work.
>
> It looks like pysvd is a pure python package with no dependencies, so it 
> should be OK to vendor it in qemu.git/python/wheels/ - look at 
> qemu.git/python/scripts/vendor.py and consider updating and running this 
> script.

Thanks, I'll look at it and add it in v2.

>
> (The real blocker here is that RPM builds are performed offline and 
> dependencies that cannot be satisfied via rpm can't be added through pip. We 
> need any one of these to be true: (A) pyvsd is available (of a sufficient 
> version) in all distro repositories we target; (B) This build feature is 
> conditional and nobody minds if it never gets enabled for RPM builds; (C) The 
> package can be vendored.)
>
> ~~js
>
> That said, you might be the first person I've seen outside of Paolo and I to 
> brave mucking around with the python build venv. You deserve a bravery 
> sticker :)
>
>>
>>  # At this point, we expect Meson to be installed and available.
>>  # We expect mkvenv or pip to have created pyvenv/bin/meson for us.
>> diff --git a/meson.build b/meson.build
>> index ec59effca2..dee587483b 100644
>> --- a/meson.build
>> +++ b/meson.build
>> @@ -3235,6 +3235,10 @@ tracetool_depends = files(
>>    'scripts/tracetool/vcpu.py'
>>  )
>>
>> +svd_gen_header = [
>> +  python, files('scripts/svd-gen-header.py')
>> +]
>> +
>>  qemu_version_cmd = [find_program('scripts/qemu-version.sh'),
>>                      meson.current_source_dir(),
>>                      get_option('pkgversion'), meson.project_version()]
>> diff --git a/python/setup.cfg b/python/setup.cfg
>> index 48668609d3..bc830c541a 100644
>> --- a/python/setup.cfg
>> +++ b/python/setup.cfg
>> @@ -45,6 +45,7 @@ devel =
>>      urwid >= 2.1.2
>>      urwid-readline >= 0.13
>>      Pygments >= 2.9.0
>> +    pysvd >= 0.2.3
>>
>>  # Provides qom-fuse functionality
>>  fuse =
>> diff --git a/python/tests/minreqs.txt b/python/tests/minreqs.txt
>> index a3f423efd8..7993fcd23c 100644
>> --- a/python/tests/minreqs.txt
>> +++ b/python/tests/minreqs.txt
>> @@ -22,6 +22,9 @@ distlib==0.3.6
>>  # Dependencies for FUSE support for qom-fuse
>>  fusepy==2.0.4
>>
>> +# Dependencies for svd-gen-regs
>> +pysvd==0.2.3
>> +
>>  # Test-runners, utilities, etc.
>>  avocado-framework==90.0
>>
>> diff --git a/pythondeps.toml b/pythondeps.toml
>> index 9c16602d30..8416b17650 100644
>> --- a/pythondeps.toml
>> +++ b/pythondeps.toml
>> @@ -32,3 +32,6 @@ sphinx_rtd_theme = { accepted = ">=0.5", installed = 
>> "1.1.1" }
>>  # avocado-framework, for example right now the limit is 92.x.
>>  avocado-framework = { accepted = "(>=88.1, <93.0)", installed = "88.1", 
>> canary = "avocado" }
>>  pycdlib = { accepted = ">=1.11.0" }
>> +
>> +[svd-gen-header]
>> +pysvd = { accepted = ">=0.2.3.", installed = "0.2.3" }
>> diff --git a/scripts/svd-gen-header.py b/scripts/svd-gen-header.py
>> new file mode 100755
>> index 0000000000..ab8cb4b665
>> --- /dev/null
>> +++ b/scripts/svd-gen-header.py
>> @@ -0,0 +1,342 @@
>> +#!/usr/bin/env python3
>> +
>> +# Copyright 2024 Google LLC
>> +#
>> +# This work is licensed under the terms of the GNU GPL, version 2 or later.
>> +# See the COPYING file in the top-level directory.
>> +#
>> +# Use this script to generate a C header file from an SVD xml
>> +#
>> +# Two mode of operations are supported: peripheral and system.
>> +#
>> +# When running in peripheral mode a header for a specific peripheral
>> +# is going to be generated. It will define a type and structure with
>> +# all of the available registers at the bitfield level. An array that
>> +# contains the reigster names indexed by address is also going to be
>> +# generated as well as a function to initialize registers to their
>> +# reset values.
>> +#
>> +# Invocation example:
>> +#
>> +# svd_gen_header -i MIMXRT595S_cm33.xml -o flexcomm.h -p FLEXCOMM0 -t 
>> FLEXCOMM
>> +#
>> +# When running in system mode a header for a specific system /
>> +# platform will be generated. It will define register base addresses
>> +# and interrupt numbers for selected peripherals.
>> +#
>> +# Invocation example:
>> +#
>> +# svd_gen_header -i MIMXRT595S_cm33.xml -o rt500.h -s RT500 -p FLEXCOMM0 \
>> +#                -p CLKCTL0 -p CLKCTL1
>> +#
>> +
>> +import argparse
>> +import datetime
>> +import re
>> +import os
>> +import sys
>> +import xml.etree.ElementTree
>> +import pysvd
>> +
>> +data_type_by_bits = {
>> +    8: "uint8_t",
>> +    16: "uint16_t",
>> +    32: "uint32_t",
>> +}
>> +
>> +
>> +def get_register_array_name_and_size(register):
>> +    """Return register name and register array size.
>> +
>> +    The SVD can define register arrays and pysvd encodes the whole set
>> +    as as regular register with their name prepended by [<array size>].
>> +
>> +    Returns a tuple with the register name and the size of the array or
>> +    zero if this is not a register set.
>> +
>> +    """
>> +
>> +    split = re.split(r"[\[\]]", register.name)
>> +    return (split[0], int(split[1]) if len(split) > 1 else 0)
>> +
>> +
>> +def generate_register(register):
>> +    """Generate register data.
>> +
>> +    This include a field for accessing the full 32bits as we as
>> +    bitfield based register fields.
>> +
>> +    """
>> +
>> +    data_type = data_type_by_bits[register.size]
>> +
>> +    out = f"    /* 0x{register.addressOffset:08X} {register.description} 
>> */\n"
>> +    out += "    union {\n"
>> +    out += f"        {data_type} {register.name};\n"
>> +    out += "        struct {\n"
>> +
>> +    fields = sorted(register.fields, key=lambda field: field.bitOffset)
>> +    last_msb = -1
>> +    for field in fields:
>> +        reserve_bits = 0
>> +        lsb = field.bitOffset
>> +        msb = field.bitWidth + lsb - 1
>> +
>> +        if last_msb == -1 and lsb > 0:
>> +            reserve_bits = lsb
>> +
>> +        if last_msb != -1 and (lsb - last_msb) > 1:
>> +            reserve_bits = lsb - last_msb - 1
>> +
>> +        if reserve_bits > 0:
>> +            out += f"            {data_type}:{reserve_bits};\n"
>> +
>> +        out += f"            /* [{msb}..{lsb}] {field.description} */\n"
>> +        out += f"            {data_type} {field.name} : {field.bitWidth};\n"
>> +
>> +        last_msb = msb
>> +
>> +    if register.size - last_msb > 1:
>> +        out += f"            {data_type}:{register.size - last_msb - 1};\n"
>> +
>> +    reg_name, reg_array_size = get_register_array_name_and_size(register)
>> +    if reg_array_size > 0:
>> +        out += f"        }} {reg_name}_b[{reg_array_size}];\n"
>> +    else:
>> +        out += f"        }} {reg_name}_b;\n"
>> +    out += "    };\n\n"
>> +
>> +    return out
>> +
>> +
>> +def generate_pos_and_msk_defines(name, periph):
>> +    """Generate Pos and Msk defines"""
>> +
>> +    out = "\n"
>> +    for reg in periph.registers:
>> +        for field in reg.fields:
>> +            reg_name, _ = get_register_array_name_and_size(reg)
>> +            field_name = f"{name}_{reg_name}_{field.name}"
>> +            out += f"#define {field_name}_Pos ({field.bitOffset}UL)\n"
>> +            mask = ((1 << field.bitWidth) - 1) << field.bitOffset
>> +            out += f"#define {field_name}_Msk (0x{mask:x}UL)\n"
>> +
>> +    return out
>> +
>> +
>> +def generate_enum_values(name, periph):
>> +    """Generate enum values"""
>> +
>> +    out = "\n"
>> +    for reg in periph.registers:
>> +        reg_name, _ = get_register_array_name_and_size(reg)
>> +        for field in reg.fields:
>> +            if hasattr(field, "enumeratedValues"):
>> +                out += "\n"
>> +                out += "typedef enum {\n"
>> +                for enum in field.enumeratedValues.enumeratedValues:
>> +                    enum_name = 
>> f"{name}_{reg_name}_{field.name}_{enum.name}"
>> +                    out += f"    /* {enum.description} */\n"
>> +                    out += f"    {enum_name} = {enum.value},\n"
>> +                out += f"}} {name}_{reg_name}_{field.name}_Enum;\n"
>> +
>> +    return out
>> +
>> +
>> +def generate_register_names_array_macro(name, periph):
>> +    """Generate register names array macro"""
>> +
>> +    out = f"#define {name}_REGISTER_NAMES_ARRAY(_name) \\\n"
>> +    out += f"    const char *_name[sizeof({name}_Type)] = {{ \\\n"
>> +    for reg in periph.registers:
>> +        reg_name, reg_array_size = get_register_array_name_and_size(reg)
>> +        if reg_array_size > 0:
>> +            for i in range(0, reg_array_size):
>> +                start = reg.addressOffset + i * reg.size // 8
>> +                stop = reg.addressOffset + (i + 1) * reg.size // 8 - 1
>> +                out += f'        [{start} ... {stop}] = "{reg_name}{i}", 
>> \\\n'
>> +        else:
>> +            start = reg.addressOffset
>> +            stop = reg.addressOffset + reg.size // 8 - 1
>> +            out += f'        [{start} ... {stop}] = "{reg.name}", \\\n'
>> +    out += "    }\n"
>> +
>> +    return out
>> +
>> +
>> +def generate_reset_registers_function(name, periph):
>> +    """Generate reset registers function"""
>> +
>> +    out = "\n"
>> +    fname = f"{name.lower()}_reset_registers"
>> +    out += f"static inline void {fname}({name}_Type *regs)\n"
>> +    out += "{\n"
>> +    for reg in periph.registers:
>> +        reg_name, reg_array_size = get_register_array_name_and_size(reg)
>> +        if reg_array_size > 0:
>> +            for i in range(0, int(reg_array_size)):
>> +                out += f"    regs->{reg_name}[{i}] = 
>> {hex(reg.resetValue)};\n"
>> +        else:
>> +            out += f"    regs->{reg_name} = {hex(reg.resetValue)};\n"
>> +    out += "}\n"
>> +
>> +    return out
>> +
>> +
>> +def generate_peripheral_header(periph, name):
>> +    """Generate peripheral header
>> +
>> +    The following information is generated:
>> +
>> +    * typedef with all of the available registers and register fields,
>> +    position and mask defines for register fields.
>> +
>> +    * enum values that encode register fields options.
>> +
>> +    * a macro that defines the register names indexed by the relative
>> +    address of the register.
>> +
>> +    * a function that sets the registers to their reset values
>> +
>> +    """
>> +
>> +    out = f"/* {name} - {periph.description} */\n\n"
>> +    out += "typedef struct {\n"
>> +
>> +    last_reg_offset = 0
>> +    cnt = 0
>> +    for reg in periph.registers:
>> +        reserved_words = 0
>> +        if (reg.addressOffset - last_reg_offset) > 0:
>> +            reserved_words = int((reg.addressOffset - last_reg_offset) // 4)
>> +            cnt += 1
>> +
>> +        if cnt:
>> +            show_count = cnt
>> +        else:
>> +            show_count = ""
>> +
>> +        if reserved_words == 1:
>> +            out += f"    uint32_t RESERVED{show_count};\n\n"
>> +        elif reserved_words > 1:
>> +            out += f"    uint32_t 
>> RESERVED{show_count}[{reserved_words}];\n\n"
>> +
>> +        out += generate_register(reg)
>> +        last_reg_offset = reg.addressOffset + reg.size // 8
>> +
>> +    size = periph.addressBlocks[0].size
>> +    out += f"}} {name}_Type; /* Size = {size} (0x{size:X}) */\n"
>> +
>> +    out += "\n\n"
>> +
>> +    out += generate_pos_and_msk_defines(name, periph)
>> +
>> +    out += generate_enum_values(name, periph)
>> +
>> +    out += generate_register_names_array_macro(name, periph)
>> +
>> +    out += generate_reset_registers_function(name, periph)
>> +
>> +    return out
>> +
>> +
>> +def get_same_class_peripherals(svd, periph):
>> +    """Get a list of peripherals that are instances of the same class."""
>> +
>> +    return [periph] + [
>> +        p
>> +        for p in svd.peripherals
>> +        if p.derivedFrom and p.derivedFrom.name == periph.name
>> +    ]
>> +
>> +
>> +def generate_system_header(system, svd, periph):
>> +    """Generate base and irq defines for given list of peripherals"""
>> +
>> +    out = ""
>> +
>> +    for p in get_same_class_peripherals(svd, periph):
>> +        out += f"#define {system}_{p.name}_BASE 0x{p.baseAddress:X}UL\n"
>> +    out += "\n"
>> +
>> +    for p in get_same_class_peripherals(svd, periph):
>> +        for irq in p.interrupts:
>> +            out += f"#define {system}_{irq.name}_IRQn 0x{irq.value}UL\n"
>> +    out += "\n"
>> +
>> +    return out
>> +
>> +
>> +def main():
>> +    """Script to generate C header file from an SVD file"""
>> +
>> +    parser = argparse.ArgumentParser()
>> +    parser.add_argument(
>> +        "-i", "--input", type=str, help="Input SVD file", required=True
>> +    )
>> +    parser.add_argument(
>> +        "-o", "--output", type=str, help="Output .h file", required=True
>> +    )
>> +    parser.add_argument(
>> +        "-p",
>> +        "--peripheral",
>> +        action="append",
>> +        help="peripheral name from the SVD file",
>> +        required=True,
>> +    )
>> +    parser.add_argument(
>> +        "-t",
>> +        "--type-name",
>> +        type=str,
>> +        help="name to be used for peripheral definitions",
>> +        required=False,
>> +    )
>> +    parser.add_argument(
>> +        "-s",
>> +        "--system",
>> +        type=str,
>> +        help="name to be used for the system definitions",
>> +        required=False,
>> +    )
>> +
>> +    args = parser.parse_args()
>> +
>> +    node = xml.etree.ElementTree.parse(args.input).getroot()
>> +    svd = pysvd.element.Device(node)
>> +
>> +    # Write license header
>> +    out = "/*\n"
>> +    license_text = svd.licenseText.split("\\n")
>> +    for line in license_text:
>> +        sline = f" * {line.strip()}"
>> +        out += f"{sline.rstrip()}\n"
>> +
>> +    now = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
>> +    out += f" * @note Automatically generated by 
>> {os.path.basename(__file__)}"
>> +    out += f" on {now} UTC from {os.path.basename(args.input)}.\n"
>> +    out += " *\n */\n\n"
>> +
>> +    # Write some generic defines
>> +    out += "#pragma once\n\n"
>> +
>> +    for name in args.peripheral:
>> +        periph = svd.find(name)
>> +        if periph:
>> +            if args.system:
>> +                out += generate_system_header(args.system, svd, periph)
>> +            else:
>> +                out += generate_peripheral_header(
>> +                    periph, args.type_name if args.type_name else 
>> periph.name
>> +                )
>> +        else:
>> +            print(f"No such peripheral: {name}")
>> +            return 1
>> +
>> +    with open(args.output, "w", encoding="ascii") as output:
>> +        output.write(out)
>> +
>> +    return 0
>> +
>> +
>> +if __name__ == "__main__":
>> +    sys.exit(main())
>> --
>> 2.46.0.rc2.264.g509ed76dc8-goog
>>

Reply via email to